@frigear-nu/events 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/module.d.mts +45 -7
- package/dist/module.json +1 -1
- package/dist/module.mjs +53 -42
- package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +11 -0
- package/dist/runtime/app/components/EventAdminAddonDialog.vue +113 -0
- package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +11 -0
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +11 -0
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +79 -0
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +11 -0
- package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +19 -0
- package/dist/runtime/app/components/EventAdminAddonGroups.vue +123 -0
- package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +19 -0
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +20 -0
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +175 -0
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +20 -0
- package/dist/runtime/app/components/EventCard.vue +39 -27
- package/dist/runtime/app/components/EventTicketCard.vue +5 -4
- package/dist/runtime/app/components/EventTicketPicker.vue +40 -21
- package/dist/runtime/app/composables/index.d.ts +1 -1
- package/dist/runtime/app/composables/index.js +1 -1
- package/dist/runtime/app/composables/useEvents.d.ts +10 -4
- package/dist/runtime/app/composables/useEvents.js +21 -10
- package/dist/runtime/app/pages/admin-event.d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin-event.vue +151 -0
- package/dist/runtime/app/pages/admin-event.vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin.d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin.vue +154 -0
- package/dist/runtime/app/pages/admin.vue.d.ts +3 -0
- package/dist/runtime/app/pages/event.d.vue.ts +14 -1
- package/dist/runtime/app/pages/event.vue +160 -45
- package/dist/runtime/app/pages/event.vue.d.ts +14 -1
- package/dist/runtime/app/pages/events.vue +12 -10
- package/dist/runtime/app/pages/tickets.vue +26 -19
- package/dist/runtime/app/tabs/Attendees.d.vue.ts +7 -0
- package/dist/runtime/app/tabs/Attendees.vue +88 -0
- package/dist/runtime/app/tabs/Attendees.vue.d.ts +7 -0
- package/dist/runtime/app/tabs/Details.d.vue.ts +11 -0
- package/dist/runtime/app/tabs/Details.vue +74 -0
- package/dist/runtime/app/tabs/Details.vue.d.ts +11 -0
- package/dist/runtime/app/tabs/Tickets.d.vue.ts +11 -0
- package/dist/runtime/app/tabs/Tickets.vue +164 -0
- package/dist/runtime/app/tabs/Tickets.vue.d.ts +11 -0
- package/dist/runtime/app/utils/admin-event.d.ts +48 -0
- package/dist/runtime/app/utils/admin-event.js +22 -0
- package/dist/runtime/i18n/lang/da.json +34 -1
- package/dist/runtime/i18n/lang/en.json +33 -0
- package/dist/runtime/menus.js +17 -1
- package/dist/runtime/server/db/schema.sqlite.d.ts +62 -5
- package/dist/runtime/server/db/schema.sqlite.js +27 -2
- package/dist/runtime/server/mcp/schemas.d.ts +119 -0
- package/dist/runtime/server/mcp/schemas.js +58 -0
- package/dist/runtime/server/mcp/tools/admin/create-event.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/create-event.js +11 -0
- package/dist/runtime/server/mcp/tools/admin/get-event-definition.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/get-event-definition.js +10 -0
- package/dist/runtime/server/mcp/tools/admin/list-all-events.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/list-all-events.js +9 -0
- package/dist/runtime/server/mcp/tools/admin/list-attendees.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/list-attendees.js +25 -0
- package/dist/runtime/server/mcp/tools/admin/update-event.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/update-event.js +10 -0
- package/dist/runtime/server/mcp/tools/admin/update-ticket-type.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/update-ticket-type.js +10 -0
- package/dist/runtime/server/mcp/tools/get-event.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/get-event.js +12 -0
- package/dist/runtime/server/mcp/tools/list-events.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/list-events.js +6 -0
- package/dist/runtime/server/mcp/tools/my-tickets.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/my-tickets.js +6 -0
- package/dist/runtime/server/plugins/staff-scopes.d.ts +2 -0
- package/dist/runtime/server/plugins/staff-scopes.js +1 -0
- package/dist/runtime/server/plugins/subscriptions-membership.d.ts +2 -0
- package/dist/runtime/server/plugins/subscriptions-membership.js +11 -0
- package/dist/runtime/server/routes/admin/event-clone.post.d.ts +2 -0
- package/dist/runtime/server/routes/admin/event-clone.post.js +31 -0
- package/dist/runtime/server/routes/admin/event.get.d.ts +2 -0
- package/dist/runtime/server/routes/admin/event.get.js +5 -0
- package/dist/runtime/server/routes/admin/event.patch.js +3 -8
- package/dist/runtime/server/routes/admin/events.post.js +3 -13
- package/dist/runtime/server/routes/admin/membership-options.get.d.ts +2 -0
- package/dist/runtime/server/routes/admin/membership-options.get.js +4 -0
- package/dist/runtime/server/routes/admin/ticket-type.patch.js +3 -8
- package/dist/runtime/server/routes/admin/ticket-types.post.js +2 -1
- package/dist/runtime/server/routes/public/event.get.js +2 -2
- package/dist/runtime/server/routes/public/purchase.post.js +3 -2
- package/dist/runtime/server/routes/public/ticket-pay.post.js +1 -1
- package/dist/runtime/server/routes/public/ticket.get.js +1 -1
- package/dist/runtime/server/routes/public/tickets-me.get.js +1 -1
- package/dist/runtime/server/utils/admin-schemas.d.ts +102 -20
- package/dist/runtime/server/utils/admin-schemas.js +8 -2
- package/dist/runtime/server/utils/admin.d.ts +82 -1
- package/dist/runtime/server/utils/admin.js +51 -1
- package/dist/runtime/server/utils/auth.d.ts +7 -6
- package/dist/runtime/server/utils/auth.js +4 -4
- package/dist/runtime/server/utils/catalog.d.ts +14 -0
- package/dist/runtime/server/utils/catalog.js +20 -4
- package/dist/runtime/server/utils/membership.d.ts +6 -2
- package/dist/runtime/server/utils/membership.js +15 -2
- package/dist/runtime/server/utils/tickets.js +15 -12
- package/dist/runtime/utils/index.d.ts +17 -7
- package/dist/runtime/utils/index.js +16 -19
- package/dist/types.d.mts +3 -1
- package/package.json +21 -7
|
@@ -69,6 +69,22 @@
|
|
|
69
69
|
"loading": "Loading…",
|
|
70
70
|
"members": "Members",
|
|
71
71
|
"viewEvent": "View event",
|
|
72
|
+
"picker": {
|
|
73
|
+
"soldOut": "Sold out",
|
|
74
|
+
"notStarted": "Sales not started",
|
|
75
|
+
"ended": "Sales ended",
|
|
76
|
+
"membersOnly": "Members only",
|
|
77
|
+
"notForMembers": "Not for members",
|
|
78
|
+
"booked": "Already booked",
|
|
79
|
+
"left": "left",
|
|
80
|
+
"free": "Free",
|
|
81
|
+
"included": "Included",
|
|
82
|
+
"getFree": "Get free ticket",
|
|
83
|
+
"buy": "Buy ticket",
|
|
84
|
+
"signIn": "Sign in to get a ticket.",
|
|
85
|
+
"noTickets": "No tickets are available for this event."
|
|
86
|
+
},
|
|
87
|
+
"backToEvents": "All events",
|
|
72
88
|
"hideQr": "Hide QR code",
|
|
73
89
|
"checkedIn": "Checked in",
|
|
74
90
|
"myTickets": "My tickets",
|
|
@@ -82,6 +98,23 @@
|
|
|
82
98
|
"past": {
|
|
83
99
|
"title": "Past events",
|
|
84
100
|
"description": "See past Frigear events you missed ..."
|
|
101
|
+
},
|
|
102
|
+
"menu": {
|
|
103
|
+
"tickets": {
|
|
104
|
+
"label": "Tickets",
|
|
105
|
+
"description": "Your event tickets."
|
|
106
|
+
},
|
|
107
|
+
"admin": {
|
|
108
|
+
"label": "Events",
|
|
109
|
+
"description": "Manage events, ticket types and attendees."
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"admin": {
|
|
113
|
+
"tabs": {
|
|
114
|
+
"details": "Details",
|
|
115
|
+
"tickets": "Tickets",
|
|
116
|
+
"attendees": "Attendees"
|
|
117
|
+
}
|
|
85
118
|
}
|
|
86
119
|
}
|
|
87
120
|
}
|
package/dist/runtime/menus.js
CHANGED
|
@@ -2,13 +2,29 @@ import { defineMenuItem, defineMenuSource, extendMenuItem } from "@frigear-nu/me
|
|
|
2
2
|
export default defineMenuSource(() => {
|
|
3
3
|
const config = useRuntimeConfig().public.events;
|
|
4
4
|
if (config?.pages?.tickets) {
|
|
5
|
-
|
|
5
|
+
const item = defineMenuItem({
|
|
6
6
|
id: "event-tickets",
|
|
7
7
|
label: "Tickets",
|
|
8
|
+
labelKey: "events.menu.tickets.label",
|
|
8
9
|
description: "Your event tickets.",
|
|
10
|
+
descriptionKey: "events.menu.tickets.description",
|
|
9
11
|
icon: "i-lucide-ticket",
|
|
10
12
|
to: config.pages.tickets,
|
|
11
13
|
order: 40
|
|
14
|
+
});
|
|
15
|
+
extendMenuItem("account", item);
|
|
16
|
+
extendMenuItem("dashboard.account", item);
|
|
17
|
+
}
|
|
18
|
+
if (config?.pages?.admin) {
|
|
19
|
+
extendMenuItem("admin.nav", defineMenuItem({
|
|
20
|
+
id: "events-admin",
|
|
21
|
+
label: "Events",
|
|
22
|
+
labelKey: "events.menu.admin.label",
|
|
23
|
+
description: "Manage events, ticket types and attendees.",
|
|
24
|
+
descriptionKey: "events.menu.admin.description",
|
|
25
|
+
icon: "i-lucide-calendar-days",
|
|
26
|
+
to: config.pages.admin,
|
|
27
|
+
order: 20
|
|
12
28
|
}));
|
|
13
29
|
}
|
|
14
30
|
});
|
|
@@ -120,21 +120,21 @@ export declare const events: import("drizzle-orm/sqlite-core").SQLiteTableWithCo
|
|
|
120
120
|
location: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
121
121
|
name: "location";
|
|
122
122
|
tableName: "events";
|
|
123
|
-
dataType: "
|
|
124
|
-
columnType: "
|
|
125
|
-
data:
|
|
123
|
+
dataType: "custom";
|
|
124
|
+
columnType: "SQLiteCustomColumn";
|
|
125
|
+
data: Translated;
|
|
126
126
|
driverParam: string;
|
|
127
127
|
notNull: false;
|
|
128
128
|
hasDefault: false;
|
|
129
129
|
isPrimaryKey: false;
|
|
130
130
|
isAutoincrement: false;
|
|
131
131
|
hasRuntimeDefault: false;
|
|
132
|
-
enumValues:
|
|
132
|
+
enumValues: undefined;
|
|
133
133
|
baseColumn: never;
|
|
134
134
|
identity: undefined;
|
|
135
135
|
generated: undefined;
|
|
136
136
|
}, {}, {
|
|
137
|
-
|
|
137
|
+
sqliteColumnBuilderBrand: "SQLiteCustomColumnBuilderBrand";
|
|
138
138
|
}>;
|
|
139
139
|
locationUrl: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
140
140
|
name: "locationUrl";
|
|
@@ -467,6 +467,25 @@ export declare const eventTicketTypes: import("drizzle-orm/sqlite-core").SQLiteT
|
|
|
467
467
|
identity: undefined;
|
|
468
468
|
generated: undefined;
|
|
469
469
|
}, {}, {}>;
|
|
470
|
+
saleSchedule: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
471
|
+
name: "saleSchedule";
|
|
472
|
+
tableName: "event_ticket_types";
|
|
473
|
+
dataType: "json";
|
|
474
|
+
columnType: "SQLiteTextJson";
|
|
475
|
+
data: import("@frigear-nu/module-kit/schedule").Schedule;
|
|
476
|
+
driverParam: string;
|
|
477
|
+
notNull: false;
|
|
478
|
+
hasDefault: false;
|
|
479
|
+
isPrimaryKey: false;
|
|
480
|
+
isAutoincrement: false;
|
|
481
|
+
hasRuntimeDefault: false;
|
|
482
|
+
enumValues: undefined;
|
|
483
|
+
baseColumn: never;
|
|
484
|
+
identity: undefined;
|
|
485
|
+
generated: undefined;
|
|
486
|
+
}, {}, {
|
|
487
|
+
$type: import("@frigear-nu/module-kit/schedule").Schedule;
|
|
488
|
+
}>;
|
|
470
489
|
maxPerUser: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
471
490
|
name: "maxPerUser";
|
|
472
491
|
tableName: "event_ticket_types";
|
|
@@ -503,6 +522,25 @@ export declare const eventTicketTypes: import("drizzle-orm/sqlite-core").SQLiteT
|
|
|
503
522
|
}, {}, {
|
|
504
523
|
$type: string[] | "any";
|
|
505
524
|
}>;
|
|
525
|
+
excludesMembership: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
526
|
+
name: "excludesMembership";
|
|
527
|
+
tableName: "event_ticket_types";
|
|
528
|
+
dataType: "json";
|
|
529
|
+
columnType: "SQLiteTextJson";
|
|
530
|
+
data: string[] | "any";
|
|
531
|
+
driverParam: string;
|
|
532
|
+
notNull: false;
|
|
533
|
+
hasDefault: false;
|
|
534
|
+
isPrimaryKey: false;
|
|
535
|
+
isAutoincrement: false;
|
|
536
|
+
hasRuntimeDefault: false;
|
|
537
|
+
enumValues: undefined;
|
|
538
|
+
baseColumn: never;
|
|
539
|
+
identity: undefined;
|
|
540
|
+
generated: undefined;
|
|
541
|
+
}, {}, {
|
|
542
|
+
$type: string[] | "any";
|
|
543
|
+
}>;
|
|
506
544
|
isDefault: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
507
545
|
name: "isDefault";
|
|
508
546
|
tableName: "event_ticket_types";
|
|
@@ -1021,6 +1059,25 @@ export declare const eventTickets: import("drizzle-orm/sqlite-core").SQLiteTable
|
|
|
1021
1059
|
}, {}, {
|
|
1022
1060
|
length: undefined;
|
|
1023
1061
|
}>;
|
|
1062
|
+
locale: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
1063
|
+
name: "locale";
|
|
1064
|
+
tableName: "event_tickets";
|
|
1065
|
+
dataType: "string";
|
|
1066
|
+
columnType: "SQLiteText";
|
|
1067
|
+
data: string;
|
|
1068
|
+
driverParam: string;
|
|
1069
|
+
notNull: false;
|
|
1070
|
+
hasDefault: false;
|
|
1071
|
+
isPrimaryKey: false;
|
|
1072
|
+
isAutoincrement: false;
|
|
1073
|
+
hasRuntimeDefault: false;
|
|
1074
|
+
enumValues: [string, ...string[]];
|
|
1075
|
+
baseColumn: never;
|
|
1076
|
+
identity: undefined;
|
|
1077
|
+
generated: undefined;
|
|
1078
|
+
}, {}, {
|
|
1079
|
+
length: undefined;
|
|
1080
|
+
}>;
|
|
1024
1081
|
checkedInAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
1025
1082
|
name: "checkedInAt";
|
|
1026
1083
|
tableName: "event_tickets";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sqliteTable, text, integer, index, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
1
|
+
import { sqliteTable, text, integer, index, uniqueIndex, customType } from "drizzle-orm/sqlite-core";
|
|
2
2
|
export const events = sqliteTable("events", {
|
|
3
3
|
/** `evn_` + random id. */
|
|
4
4
|
id: text().primaryKey(),
|
|
@@ -8,7 +8,23 @@ export const events = sqliteTable("events", {
|
|
|
8
8
|
/** Markdown body (per locale). */
|
|
9
9
|
body: text({ mode: "json" }).$type(),
|
|
10
10
|
image: text(),
|
|
11
|
-
|
|
11
|
+
// A plain `text` column that also holds a `Translated`: strings are stored as they are (so rows written before
|
|
12
|
+
// locations could be translated keep working), per-locale maps as json. No schema change, hence no migration.
|
|
13
|
+
// Kept inline: module-kit lifts each table's columns into the host schema, so helpers declared above would be lost.
|
|
14
|
+
location: customType({
|
|
15
|
+
dataType: () => "text",
|
|
16
|
+
toDriver: (value) => typeof value === "string" ? value : JSON.stringify(value),
|
|
17
|
+
fromDriver: (value) => {
|
|
18
|
+
if (value.startsWith("{")) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(value);
|
|
21
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
})(),
|
|
12
28
|
locationUrl: text(),
|
|
13
29
|
startsAt: integer({ mode: "timestamp" }).notNull(),
|
|
14
30
|
endsAt: integer({ mode: "timestamp" }),
|
|
@@ -37,9 +53,16 @@ export const eventTicketTypes = sqliteTable("event_ticket_types", {
|
|
|
37
53
|
capacity: integer(),
|
|
38
54
|
saleStartsAt: integer({ mode: "timestamp" }),
|
|
39
55
|
saleEndsAt: integer({ mode: "timestamp" }),
|
|
56
|
+
/** Optional recurring sale season (`yearly`) or fixed `range`, checked on top of `saleStartsAt`/`saleEndsAt`; null = always. */
|
|
57
|
+
saleSchedule: text({ mode: "json" }).$type(),
|
|
40
58
|
maxPerUser: integer().notNull().default(1),
|
|
41
59
|
/** `null` = open to everyone, `'any'` = any active subscription, or a list of plan keys. */
|
|
42
60
|
requiresMembership: text({ mode: "json" }).$type(),
|
|
61
|
+
/**
|
|
62
|
+
* The opposite: holders of these plans can NOT buy this ticket (`'any'` = any active subscription), so a
|
|
63
|
+
* member price and a non-member price can be separate ticket types. `null` = nobody is excluded.
|
|
64
|
+
*/
|
|
65
|
+
excludesMembership: text({ mode: "json" }).$type(),
|
|
43
66
|
isDefault: integer({ mode: "boolean" }).notNull().default(false),
|
|
44
67
|
sortOrder: integer().notNull().default(0),
|
|
45
68
|
createdAt: integer({ mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
@@ -89,6 +112,8 @@ export const eventTickets = sqliteTable("event_tickets", {
|
|
|
89
112
|
currency: text().notNull(),
|
|
90
113
|
/** Unguessable value shown as a QR code and scanned at check-in. */
|
|
91
114
|
code: text().notNull().unique(),
|
|
115
|
+
/** Language the buyer was browsing in; used for mails and payment texts after the purchase. */
|
|
116
|
+
locale: text(),
|
|
92
117
|
checkedInAt: integer({ mode: "timestamp" }),
|
|
93
118
|
checkedInBy: integer(),
|
|
94
119
|
createdAt: integer({ mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ticketTypeFields: z.ZodObject<{
|
|
3
|
+
currency: z.ZodOptional<z.ZodString>;
|
|
4
|
+
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
5
|
+
key: z.ZodString;
|
|
6
|
+
price: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
hidePrice: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
9
|
+
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
10
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
11
|
+
saleStartsAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
12
|
+
saleEndsAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
13
|
+
maxPerUser: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
excludesMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
15
|
+
isDefault: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
+
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export declare const eventFields: {
|
|
19
|
+
slug: z.ZodString;
|
|
20
|
+
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
21
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
22
|
+
body: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
23
|
+
image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
24
|
+
location: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
25
|
+
locationUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
26
|
+
startsAt: z.ZodString;
|
|
27
|
+
endsAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
29
|
+
draft: "draft";
|
|
30
|
+
published: "published";
|
|
31
|
+
archived: "archived";
|
|
32
|
+
}>>;
|
|
33
|
+
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
34
|
+
};
|
|
35
|
+
export declare const createEventShape: {
|
|
36
|
+
ticketTypes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
37
|
+
key: z.ZodString;
|
|
38
|
+
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
39
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
40
|
+
price: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
currency: z.ZodOptional<z.ZodString>;
|
|
42
|
+
hidePrice: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
44
|
+
saleStartsAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
45
|
+
saleEndsAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
46
|
+
maxPerUser: z.ZodOptional<z.ZodNumber>;
|
|
47
|
+
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
48
|
+
excludesMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
49
|
+
isDefault: z.ZodOptional<z.ZodBoolean>;
|
|
50
|
+
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
addonGroups: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
52
|
+
title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
53
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
54
|
+
multiple: z.ZodOptional<z.ZodBoolean>;
|
|
55
|
+
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
56
|
+
addons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
57
|
+
label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
58
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
59
|
+
price: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
61
|
+
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
}, z.core.$strip>>>;
|
|
63
|
+
}, z.core.$strip>>>;
|
|
64
|
+
}, z.core.$strip>>>;
|
|
65
|
+
slug: z.ZodString;
|
|
66
|
+
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
67
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
68
|
+
body: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
69
|
+
image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
70
|
+
location: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
71
|
+
locationUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
72
|
+
startsAt: z.ZodString;
|
|
73
|
+
endsAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
74
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
75
|
+
draft: "draft";
|
|
76
|
+
published: "published";
|
|
77
|
+
archived: "archived";
|
|
78
|
+
}>>;
|
|
79
|
+
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
80
|
+
};
|
|
81
|
+
export declare const updateEventShape: {
|
|
82
|
+
id: z.ZodString;
|
|
83
|
+
patch: z.ZodObject<{
|
|
84
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
85
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
86
|
+
description: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>>;
|
|
87
|
+
body: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>>;
|
|
88
|
+
image: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
89
|
+
location: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>>;
|
|
90
|
+
locationUrl: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
91
|
+
startsAt: z.ZodOptional<z.ZodString>;
|
|
92
|
+
endsAt: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
93
|
+
status: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
|
|
94
|
+
draft: "draft";
|
|
95
|
+
published: "published";
|
|
96
|
+
archived: "archived";
|
|
97
|
+
}>>>;
|
|
98
|
+
requiresMembership: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>>;
|
|
99
|
+
}, z.core.$strip>;
|
|
100
|
+
};
|
|
101
|
+
export declare const updateTicketTypeShape: {
|
|
102
|
+
id: z.ZodString;
|
|
103
|
+
patch: z.ZodObject<{
|
|
104
|
+
currency: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
105
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
106
|
+
key: z.ZodOptional<z.ZodString>;
|
|
107
|
+
price: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
108
|
+
hidePrice: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
109
|
+
description: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>>;
|
|
110
|
+
requiresMembership: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>>;
|
|
111
|
+
capacity: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
112
|
+
saleStartsAt: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
113
|
+
saleEndsAt: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
114
|
+
maxPerUser: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
115
|
+
excludesMembership: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>>;
|
|
116
|
+
isDefault: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
117
|
+
sortOrder: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
118
|
+
}, z.core.$strip>;
|
|
119
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const text = z.union([z.string().min(1), z.record(z.string(), z.string())]).describe('Text in one language, or a locale -> text map such as {"en": "Party", "da": "Fest"}.');
|
|
3
|
+
const membership = z.union([z.literal("any"), z.array(z.string())]).nullable().describe('"any", a list of plan keys, or null for no rule.');
|
|
4
|
+
const isoDate = z.string().describe("ISO 8601 date-time, e.g. 2026-12-24T18:00:00Z.");
|
|
5
|
+
const addon = z.object({
|
|
6
|
+
label: text,
|
|
7
|
+
description: text.nullish(),
|
|
8
|
+
price: z.number().int().min(0).optional().describe("Minor units (\xF8re/cents)."),
|
|
9
|
+
capacity: z.number().int().min(0).nullish(),
|
|
10
|
+
sortOrder: z.number().int().optional()
|
|
11
|
+
});
|
|
12
|
+
const addonGroup = z.object({
|
|
13
|
+
title: text,
|
|
14
|
+
required: z.boolean().optional(),
|
|
15
|
+
multiple: z.boolean().optional(),
|
|
16
|
+
sortOrder: z.number().int().optional(),
|
|
17
|
+
addons: z.array(addon).optional()
|
|
18
|
+
});
|
|
19
|
+
const ticketType = z.object({
|
|
20
|
+
key: z.string().min(1).max(64),
|
|
21
|
+
name: text,
|
|
22
|
+
description: text.nullish(),
|
|
23
|
+
price: z.number().int().min(0).optional().describe("Minor units (\xF8re/cents)."),
|
|
24
|
+
currency: z.string().length(3).optional(),
|
|
25
|
+
hidePrice: z.boolean().optional(),
|
|
26
|
+
capacity: z.number().int().min(0).nullish(),
|
|
27
|
+
saleStartsAt: isoDate.nullish(),
|
|
28
|
+
saleEndsAt: isoDate.nullish(),
|
|
29
|
+
maxPerUser: z.number().int().min(1).optional(),
|
|
30
|
+
requiresMembership: membership.optional(),
|
|
31
|
+
excludesMembership: membership.optional(),
|
|
32
|
+
isDefault: z.boolean().optional(),
|
|
33
|
+
sortOrder: z.number().int().optional(),
|
|
34
|
+
addonGroups: z.array(addonGroup).optional()
|
|
35
|
+
});
|
|
36
|
+
export const ticketTypeFields = ticketType.omit({ addonGroups: true });
|
|
37
|
+
export const eventFields = {
|
|
38
|
+
slug: z.string().min(1).max(128).describe("Lowercase letters, numbers and dashes."),
|
|
39
|
+
name: text,
|
|
40
|
+
description: text.nullish(),
|
|
41
|
+
body: text.nullish(),
|
|
42
|
+
image: z.string().nullish(),
|
|
43
|
+
location: text.nullish(),
|
|
44
|
+
locationUrl: z.string().nullish(),
|
|
45
|
+
startsAt: isoDate,
|
|
46
|
+
endsAt: isoDate.nullish(),
|
|
47
|
+
status: z.enum(["draft", "published", "archived"]).optional(),
|
|
48
|
+
requiresMembership: membership.optional()
|
|
49
|
+
};
|
|
50
|
+
export const createEventShape = { ...eventFields, ticketTypes: z.array(ticketType).optional() };
|
|
51
|
+
export const updateEventShape = {
|
|
52
|
+
id: z.string().describe("The event id."),
|
|
53
|
+
patch: z.object(eventFields).partial().describe("Only the fields to change.")
|
|
54
|
+
};
|
|
55
|
+
export const updateTicketTypeShape = {
|
|
56
|
+
id: z.string().describe("The ticket type id (see get-event or list-all-events)."),
|
|
57
|
+
patch: ticketTypeFields.partial().describe("Only the fields to change. Addon groups are not changed here.")
|
|
58
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createEventWithCatalog } from "../../../utils/admin.js";
|
|
2
|
+
import { eventInput } from "../../../utils/admin-schemas.js";
|
|
3
|
+
import { createEventShape } from "../../schemas.js";
|
|
4
|
+
export default defineScopedMcpTool({
|
|
5
|
+
description: "Create an event, optionally with ticket types, addon groups and addons (staff).",
|
|
6
|
+
scopes: ["events:manage"],
|
|
7
|
+
gate: canManageEvents,
|
|
8
|
+
annotations: { destructiveHint: false },
|
|
9
|
+
inputSchema: createEventShape,
|
|
10
|
+
handler: (args) => createEventWithCatalog(eventInput.parse(args))
|
|
11
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getEventWithTicketTypes } from "../../../utils/admin.js";
|
|
3
|
+
export default defineScopedMcpTool({
|
|
4
|
+
description: "Get one event with all its ticket types and their ids, whatever its status (staff). Use the ids with update-ticket-type.",
|
|
5
|
+
scopes: ["events:manage"],
|
|
6
|
+
gate: canManageEvents,
|
|
7
|
+
annotations: { readOnlyHint: true },
|
|
8
|
+
inputSchema: { id: z.string().describe("The event id.") },
|
|
9
|
+
handler: ({ id }) => getEventWithTicketTypes(id)
|
|
10
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { db, schema } from "@nuxthub/db";
|
|
2
|
+
import { desc } from "drizzle-orm";
|
|
3
|
+
export default defineScopedMcpTool({
|
|
4
|
+
description: "List all events including drafts and archived ones (staff).",
|
|
5
|
+
scopes: ["events:manage"],
|
|
6
|
+
gate: canManageEvents,
|
|
7
|
+
annotations: { readOnlyHint: true },
|
|
8
|
+
handler: () => db.select().from(schema.events).orderBy(desc(schema.events.startsAt))
|
|
9
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { db, schema } from "@nuxthub/db";
|
|
3
|
+
import { asc, eq } from "drizzle-orm";
|
|
4
|
+
export default defineScopedMcpTool({
|
|
5
|
+
description: "List the tickets of an event (staff). Attendee names and emails are only included with the events:pii scope.",
|
|
6
|
+
scopes: ["events:manage"],
|
|
7
|
+
gate: canManageEvents,
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
inputSchema: { eventId: z.string().describe("The event id.") },
|
|
10
|
+
handler: async ({ eventId }, ctx) => {
|
|
11
|
+
const rows = await db.select({
|
|
12
|
+
ticket: schema.eventTickets,
|
|
13
|
+
userName: schema.users.name,
|
|
14
|
+
userEmail: schema.users.email
|
|
15
|
+
}).from(schema.eventTickets).leftJoin(schema.users, eq(schema.eventTickets.userId, schema.users.id)).where(eq(schema.eventTickets.eventId, eventId)).orderBy(asc(schema.eventTickets.createdAt));
|
|
16
|
+
const allow = ctx.hasScope("events:pii");
|
|
17
|
+
const attendees = rows.map(({ ticket, userName, userEmail }) => redactPii({
|
|
18
|
+
...pick(ticket, ["id", "ticketTypeId", "status", "amount", "currency", "checkedInAt", "createdAt"]),
|
|
19
|
+
addons: ticket.addons.length,
|
|
20
|
+
userName,
|
|
21
|
+
userEmail
|
|
22
|
+
}, { allow, fields: ["userName", "userEmail"] }));
|
|
23
|
+
return { notice: piiNotice(allow, "events:pii", "Attendee names and emails"), attendees };
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { eventPatchSchema, patchEventRow } from "../../../utils/admin.js";
|
|
2
|
+
import { updateEventShape } from "../../schemas.js";
|
|
3
|
+
export default defineScopedMcpTool({
|
|
4
|
+
description: "Update fields of an event (staff). Only the given fields change.",
|
|
5
|
+
scopes: ["events:manage"],
|
|
6
|
+
gate: canManageEvents,
|
|
7
|
+
annotations: { idempotentHint: true },
|
|
8
|
+
inputSchema: updateEventShape,
|
|
9
|
+
handler: ({ id, patch }) => patchEventRow(id, eventPatchSchema.parse(patch))
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { patchTicketTypeRow, ticketTypePatchSchema } from "../../../utils/admin.js";
|
|
2
|
+
import { updateTicketTypeShape } from "../../schemas.js";
|
|
3
|
+
export default defineScopedMcpTool({
|
|
4
|
+
description: "Update fields of an event's ticket type, such as price, capacity or sale dates (staff). Only the given fields change.",
|
|
5
|
+
scopes: ["events:manage"],
|
|
6
|
+
gate: canManageEvents,
|
|
7
|
+
annotations: { idempotentHint: true },
|
|
8
|
+
inputSchema: updateTicketTypeShape,
|
|
9
|
+
handler: ({ id, patch }) => patchTicketTypeRow(id, ticketTypePatchSchema.parse(patch))
|
|
10
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export default defineScopedMcpTool({
|
|
3
|
+
description: "Get a published event with its ticket types and what you can buy.",
|
|
4
|
+
scopes: ["tickets:read"],
|
|
5
|
+
annotations: { readOnlyHint: true },
|
|
6
|
+
inputSchema: { slug: z.string().describe("The event slug.") },
|
|
7
|
+
handler: async ({ slug }, { user }) => {
|
|
8
|
+
const row = await getPublishedEventRow(slug);
|
|
9
|
+
if (!row) throw createError({ statusCode: 404, statusMessage: "Event not found." });
|
|
10
|
+
return getEventDetail(row, user.id);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default defineStaffScopes(["events:manage", "events:pii"], (event, userId) => canManageEvents(event, { user: { id: userId } }));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { hasActiveSubscription } from "@frigear-nu/subscriptions/utils";
|
|
2
|
+
export default defineNitroPlugin((nitro) => {
|
|
3
|
+
nitro.hooks.hook("events:membership:check", async (ctx) => {
|
|
4
|
+
if (await hasActiveSubscription(ctx.userId, ctx.requirement === "any" ? {} : { plans: ctx.requirement })) ctx.satisfied = true;
|
|
5
|
+
});
|
|
6
|
+
nitro.hooks.hook("events:membership:options", (ctx) => {
|
|
7
|
+
const plans = Object.entries(useRuntimeConfig().subscriptions?.plans ?? {}).sort(([, a], [, b]) => a.rank - b.rank);
|
|
8
|
+
ctx.available = true;
|
|
9
|
+
ctx.options.push(...plans.map(([key, plan]) => ({ value: key, label: plan.label ?? key })));
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { asc, eq, inArray } from "drizzle-orm";
|
|
2
|
+
import { db, schema } from "@nuxthub/db";
|
|
3
|
+
import { newId } from "../../../utils/index.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
await requireEventsManager(event);
|
|
6
|
+
const id = getRouterParam(event, "id");
|
|
7
|
+
const [source] = await db.select().from(schema.events).where(eq(schema.events.id, id)).limit(1);
|
|
8
|
+
if (!source) throw createError({ statusCode: 404, statusMessage: "Event not found." });
|
|
9
|
+
const taken = async (slug2) => (await db.select({ id: schema.events.id }).from(schema.events).where(eq(schema.events.slug, slug2)).limit(1)).length > 0;
|
|
10
|
+
let slug = `${source.slug}-copy`;
|
|
11
|
+
for (let n = 2; await taken(slug); n++) slug = `${source.slug}-copy-${n}`;
|
|
12
|
+
const { id: _id, createdAt: _c, updatedAt: _u, ...fields } = source;
|
|
13
|
+
const [copy] = await db.insert(schema.events).values({ ...fields, id: newId("evn"), slug, status: "draft" }).returning();
|
|
14
|
+
const types = await db.select().from(schema.eventTicketTypes).where(eq(schema.eventTicketTypes.eventId, id)).orderBy(asc(schema.eventTicketTypes.sortOrder));
|
|
15
|
+
const groups = types.length ? await db.select().from(schema.eventAddonGroups).where(inArray(schema.eventAddonGroups.ticketTypeId, types.map((t) => t.id))).orderBy(asc(schema.eventAddonGroups.sortOrder)) : [];
|
|
16
|
+
const addons = groups.length ? await db.select().from(schema.eventAddons).where(inArray(schema.eventAddons.groupId, groups.map((g) => g.id))).orderBy(asc(schema.eventAddons.sortOrder)) : [];
|
|
17
|
+
for (const type of types) {
|
|
18
|
+
const { id: _t, createdAt: _tc, updatedAt: _tu, ...typeFields } = type;
|
|
19
|
+
const [newType] = await db.insert(schema.eventTicketTypes).values({ ...typeFields, id: newId("ttp"), eventId: copy.id }).returning();
|
|
20
|
+
for (const group of groups.filter((g) => g.ticketTypeId === type.id)) {
|
|
21
|
+
const { id: _g, ...groupFields } = group;
|
|
22
|
+
const [newGroup] = await db.insert(schema.eventAddonGroups).values({ ...groupFields, id: newId("agr"), ticketTypeId: newType.id }).returning();
|
|
23
|
+
for (const addon of addons.filter((a) => a.groupId === group.id)) {
|
|
24
|
+
const { id: _a, ...addonFields } = addon;
|
|
25
|
+
await db.insert(schema.eventAddons).values({ ...addonFields, id: newId("adn"), groupId: newGroup.id });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
setResponseStatus(event, 201);
|
|
30
|
+
return copy;
|
|
31
|
+
});
|