@frigear-nu/events 0.0.7 → 0.0.9
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 +59 -41
- 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/app/widgets/UpcomingEvents.d.vue.ts +3 -0
- package/dist/runtime/app/widgets/UpcomingEvents.vue +39 -0
- package/dist/runtime/app/widgets/UpcomingEvents.vue.d.ts +3 -0
- package/dist/runtime/app/widgets/UpcomingEventsStat.d.vue.ts +3 -0
- package/dist/runtime/app/widgets/UpcomingEventsStat.vue +17 -0
- package/dist/runtime/app/widgets/UpcomingEventsStat.vue.d.ts +3 -0
- package/dist/runtime/i18n/lang/da.json +33 -0
- package/dist/runtime/i18n/lang/en.json +33 -0
- package/dist/runtime/menus.js +15 -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/money.d.ts +24 -0
- package/dist/runtime/server/mcp/money.js +42 -0
- package/dist/runtime/server/mcp/schemas.d.ts +119 -0
- package/dist/runtime/server/mcp/schemas.js +59 -0
- package/dist/runtime/server/mcp/tools/admin/create-event.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/create-event.js +12 -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 +11 -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 +26 -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 +17 -0
- package/dist/runtime/server/mcp/tools/get-event.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/get-event.js +13 -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 +7 -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/admin/upcoming.get.d.ts +2 -0
- package/dist/runtime/server/routes/admin/upcoming.get.js +17 -0
- 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 +20 -7
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -73,6 +73,22 @@
|
|
|
73
73
|
"loading": "Henter…",
|
|
74
74
|
"members": "Medlemmer",
|
|
75
75
|
"viewEvent": "Se event",
|
|
76
|
+
"picker": {
|
|
77
|
+
"soldOut": "Udsolgt",
|
|
78
|
+
"notStarted": "Salget er ikke startet",
|
|
79
|
+
"ended": "Salget er slut",
|
|
80
|
+
"membersOnly": "Kun for medlemmer",
|
|
81
|
+
"notForMembers": "Ikke for medlemmer",
|
|
82
|
+
"booked": "Allerede booket",
|
|
83
|
+
"left": "tilbage",
|
|
84
|
+
"free": "Gratis",
|
|
85
|
+
"included": "Inkluderet",
|
|
86
|
+
"getFree": "Få gratis billet",
|
|
87
|
+
"buy": "Køb billet",
|
|
88
|
+
"signIn": "Log ind for at få en billet.",
|
|
89
|
+
"noTickets": "Der er ingen billetter til dette event."
|
|
90
|
+
},
|
|
91
|
+
"backToEvents": "Alle events",
|
|
76
92
|
"hideQr": "Skjul QR-kode",
|
|
77
93
|
"checkedIn": "Tjekket ind",
|
|
78
94
|
"myTickets": "Mine billetter",
|
|
@@ -87,6 +103,23 @@
|
|
|
87
103
|
"tickets": {
|
|
88
104
|
"label": "Billetter",
|
|
89
105
|
"description": "Dine billetter til arrangementer."
|
|
106
|
+
},
|
|
107
|
+
"admin": {
|
|
108
|
+
"label": "Arrangementer",
|
|
109
|
+
"description": "Administrér arrangementer, billettyper og deltagere."
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"admin": {
|
|
113
|
+
"tabs": {
|
|
114
|
+
"details": "Detaljer",
|
|
115
|
+
"tickets": "Billetter",
|
|
116
|
+
"attendees": "Deltagere"
|
|
117
|
+
},
|
|
118
|
+
"upcoming": {
|
|
119
|
+
"stat": "Kommende arrangementer",
|
|
120
|
+
"title": "Kommende arrangementer",
|
|
121
|
+
"empty": "Ingen kommende arrangementer.",
|
|
122
|
+
"all": "Alle arrangementer"
|
|
90
123
|
}
|
|
91
124
|
}
|
|
92
125
|
}
|
|
@@ -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",
|
|
@@ -87,6 +103,23 @@
|
|
|
87
103
|
"tickets": {
|
|
88
104
|
"label": "Tickets",
|
|
89
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
|
+
},
|
|
118
|
+
"upcoming": {
|
|
119
|
+
"stat": "Upcoming events",
|
|
120
|
+
"title": "Upcoming events",
|
|
121
|
+
"empty": "No upcoming events.",
|
|
122
|
+
"all": "All events"
|
|
90
123
|
}
|
|
91
124
|
}
|
|
92
125
|
}
|
package/dist/runtime/menus.js
CHANGED
|
@@ -2,7 +2,7 @@ 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
8
|
labelKey: "events.menu.tickets.label",
|
|
@@ -11,6 +11,20 @@ export default defineMenuSource(() => {
|
|
|
11
11
|
icon: "i-lucide-ticket",
|
|
12
12
|
to: config.pages.tickets,
|
|
13
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
|
|
14
28
|
}));
|
|
15
29
|
}
|
|
16
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,24 @@
|
|
|
1
|
+
type Priced = {
|
|
2
|
+
price?: number | null;
|
|
3
|
+
};
|
|
4
|
+
type TypeLike = Priced & {
|
|
5
|
+
currency?: string | null;
|
|
6
|
+
addonGroups?: Array<{
|
|
7
|
+
addons?: Priced[];
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
/** Output: a ticket type (DTO or row) with its price and add-on prices in whole units, currency upper-cased. */
|
|
11
|
+
export declare function ticketTypeToMajor<T extends TypeLike>(type: T): T;
|
|
12
|
+
/** Output: an event with `ticketTypes` converted by `ticketTypeToMajor`. */
|
|
13
|
+
export declare function eventToMajor<T extends {
|
|
14
|
+
ticketTypes: TypeLike[];
|
|
15
|
+
}>(event: T): T;
|
|
16
|
+
/** Output: a ticket (or attendee) with `amount` (and add-on prices) in whole units of its own currency. */
|
|
17
|
+
export declare function ticketToMajor<T extends {
|
|
18
|
+
amount: number;
|
|
19
|
+
currency: string;
|
|
20
|
+
addons?: Array<Priced & Record<string, unknown>>;
|
|
21
|
+
}>(ticket: T): T;
|
|
22
|
+
/** Input: a ticket type payload (agent, whole units) to minor units, using `fallbackCurrency` when it names none. */
|
|
23
|
+
export declare function ticketTypeToMinor<T extends TypeLike>(type: T, fallbackCurrency?: string): T;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { fromCents, toCents } from "@frigear-nu/mcp/utils";
|
|
2
|
+
import { DEFAULT_CURRENCY } from "../../utils/index.js";
|
|
3
|
+
const currencyOf = (type) => (type.currency ?? DEFAULT_CURRENCY).toUpperCase();
|
|
4
|
+
export function ticketTypeToMajor(type) {
|
|
5
|
+
const currency = currencyOf(type);
|
|
6
|
+
return {
|
|
7
|
+
...type,
|
|
8
|
+
...typeof type.price === "number" ? { price: fromCents(type.price, currency) } : {},
|
|
9
|
+
...type.currency ? { currency } : {},
|
|
10
|
+
...type.addonGroups ? {
|
|
11
|
+
addonGroups: type.addonGroups.map((group) => ({
|
|
12
|
+
...group,
|
|
13
|
+
...group.addons ? { addons: group.addons.map((addon) => typeof addon.price === "number" ? { ...addon, price: fromCents(addon.price, currency) } : addon) } : {}
|
|
14
|
+
}))
|
|
15
|
+
} : {}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function eventToMajor(event) {
|
|
19
|
+
return { ...event, ticketTypes: event.ticketTypes.map(ticketTypeToMajor) };
|
|
20
|
+
}
|
|
21
|
+
export function ticketToMajor(ticket) {
|
|
22
|
+
const currency = ticket.currency.toUpperCase();
|
|
23
|
+
return {
|
|
24
|
+
...ticket,
|
|
25
|
+
currency,
|
|
26
|
+
amount: fromCents(ticket.amount, currency),
|
|
27
|
+
...Array.isArray(ticket.addons) ? { addons: ticket.addons.map((addon) => typeof addon.price === "number" ? { ...addon, price: fromCents(addon.price, currency) } : addon) } : {}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function ticketTypeToMinor(type, fallbackCurrency = DEFAULT_CURRENCY) {
|
|
31
|
+
const currency = (type.currency ?? fallbackCurrency).toUpperCase();
|
|
32
|
+
return {
|
|
33
|
+
...type,
|
|
34
|
+
...typeof type.price === "number" ? { price: toCents(type.price, currency) } : {},
|
|
35
|
+
...type.addonGroups ? {
|
|
36
|
+
addonGroups: type.addonGroups.map((group) => ({
|
|
37
|
+
...group,
|
|
38
|
+
...group.addons ? { addons: group.addons.map((addon) => typeof addon.price === "number" ? { ...addon, price: toCents(addon.price, currency) } : addon) } : {}
|
|
39
|
+
}))
|
|
40
|
+
} : {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -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,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { MONEY_NOTICE } from "@frigear-nu/mcp/utils";
|
|
3
|
+
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"}.');
|
|
4
|
+
const membership = z.union([z.literal("any"), z.array(z.string())]).nullable().describe('"any", a list of plan keys, or null for no rule.');
|
|
5
|
+
const isoDate = z.string().describe("ISO 8601 date-time, e.g. 2026-12-24T18:00:00Z.");
|
|
6
|
+
const addon = z.object({
|
|
7
|
+
label: text,
|
|
8
|
+
description: text.nullish(),
|
|
9
|
+
price: z.number().min(0).optional().describe(`${MONEY_NOTICE} In the ticket type's currency.`),
|
|
10
|
+
capacity: z.number().int().min(0).nullish(),
|
|
11
|
+
sortOrder: z.number().int().optional()
|
|
12
|
+
});
|
|
13
|
+
const addonGroup = z.object({
|
|
14
|
+
title: text,
|
|
15
|
+
required: z.boolean().optional(),
|
|
16
|
+
multiple: z.boolean().optional(),
|
|
17
|
+
sortOrder: z.number().int().optional(),
|
|
18
|
+
addons: z.array(addon).optional()
|
|
19
|
+
});
|
|
20
|
+
const ticketType = z.object({
|
|
21
|
+
key: z.string().min(1).max(64),
|
|
22
|
+
name: text,
|
|
23
|
+
description: text.nullish(),
|
|
24
|
+
price: z.number().min(0).optional().describe(MONEY_NOTICE),
|
|
25
|
+
currency: z.string().length(3).optional().describe("ISO 4217 code such as DKK or USD. The price is in this currency; defaults to DKK for new ticket types."),
|
|
26
|
+
hidePrice: z.boolean().optional(),
|
|
27
|
+
capacity: z.number().int().min(0).nullish(),
|
|
28
|
+
saleStartsAt: isoDate.nullish(),
|
|
29
|
+
saleEndsAt: isoDate.nullish(),
|
|
30
|
+
maxPerUser: z.number().int().min(1).optional(),
|
|
31
|
+
requiresMembership: membership.optional(),
|
|
32
|
+
excludesMembership: membership.optional(),
|
|
33
|
+
isDefault: z.boolean().optional(),
|
|
34
|
+
sortOrder: z.number().int().optional(),
|
|
35
|
+
addonGroups: z.array(addonGroup).optional()
|
|
36
|
+
});
|
|
37
|
+
export const ticketTypeFields = ticketType.omit({ addonGroups: true });
|
|
38
|
+
export const eventFields = {
|
|
39
|
+
slug: z.string().min(1).max(128).describe("Lowercase letters, numbers and dashes."),
|
|
40
|
+
name: text,
|
|
41
|
+
description: text.nullish(),
|
|
42
|
+
body: text.nullish(),
|
|
43
|
+
image: z.string().nullish(),
|
|
44
|
+
location: text.nullish(),
|
|
45
|
+
locationUrl: z.string().nullish(),
|
|
46
|
+
startsAt: isoDate,
|
|
47
|
+
endsAt: isoDate.nullish(),
|
|
48
|
+
status: z.enum(["draft", "published", "archived"]).optional(),
|
|
49
|
+
requiresMembership: membership.optional()
|
|
50
|
+
};
|
|
51
|
+
export const createEventShape = { ...eventFields, ticketTypes: z.array(ticketType).optional() };
|
|
52
|
+
export const updateEventShape = {
|
|
53
|
+
id: z.string().describe("The event id."),
|
|
54
|
+
patch: z.object(eventFields).partial().describe("Only the fields to change.")
|
|
55
|
+
};
|
|
56
|
+
export const updateTicketTypeShape = {
|
|
57
|
+
id: z.string().describe("The ticket type id (see get-event or list-all-events)."),
|
|
58
|
+
patch: ticketTypeFields.partial().describe("Only the fields to change. Addon groups are not changed here.")
|
|
59
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createEventWithCatalog } from "../../../utils/admin.js";
|
|
2
|
+
import { eventInput } from "../../../utils/admin-schemas.js";
|
|
3
|
+
import { createEventShape } from "../../schemas.js";
|
|
4
|
+
import { ticketTypeToMinor } from "../../money.js";
|
|
5
|
+
export default defineScopedMcpTool({
|
|
6
|
+
description: "Create an event, optionally with ticket types, addon groups and addons (staff). Prices are in whole currency units (52.2 = 52.20), not cents.",
|
|
7
|
+
scopes: ["events:manage"],
|
|
8
|
+
gate: canManageEvents,
|
|
9
|
+
annotations: { destructiveHint: false },
|
|
10
|
+
inputSchema: createEventShape,
|
|
11
|
+
handler: (args) => createEventWithCatalog(eventInput.parse({ ...args, ticketTypes: args.ticketTypes?.map((type) => ticketTypeToMinor(type)) }))
|
|
12
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getEventWithTicketTypes } from "../../../utils/admin.js";
|
|
3
|
+
import { eventToMajor } from "../../money.js";
|
|
4
|
+
export default defineScopedMcpTool({
|
|
5
|
+
description: "Get one event with all its ticket types and their ids, whatever its status (staff). Use the ids with update-ticket-type. Prices are in whole currency units (52.2 = 52.20), not cents.",
|
|
6
|
+
scopes: ["events:manage"],
|
|
7
|
+
gate: canManageEvents,
|
|
8
|
+
annotations: { readOnlyHint: true },
|
|
9
|
+
inputSchema: { id: z.string().describe("The event id.") },
|
|
10
|
+
handler: async ({ id }) => eventToMajor(await getEventWithTicketTypes(id))
|
|
11
|
+
});
|
|
@@ -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,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { db, schema } from "@nuxthub/db";
|
|
3
|
+
import { asc, eq } from "drizzle-orm";
|
|
4
|
+
import { ticketToMajor } from "../../money.js";
|
|
5
|
+
export default defineScopedMcpTool({
|
|
6
|
+
description: "List the tickets of an event (staff). Attendee names and emails are only included with the events:pii scope. Amounts are in whole currency units (52.2 = 52.20), not cents.",
|
|
7
|
+
scopes: ["events:manage"],
|
|
8
|
+
gate: canManageEvents,
|
|
9
|
+
annotations: { readOnlyHint: true },
|
|
10
|
+
inputSchema: { eventId: z.string().describe("The event id.") },
|
|
11
|
+
handler: async ({ eventId }, ctx) => {
|
|
12
|
+
const rows = await db.select({
|
|
13
|
+
ticket: schema.eventTickets,
|
|
14
|
+
userName: schema.users.name,
|
|
15
|
+
userEmail: schema.users.email
|
|
16
|
+
}).from(schema.eventTickets).leftJoin(schema.users, eq(schema.eventTickets.userId, schema.users.id)).where(eq(schema.eventTickets.eventId, eventId)).orderBy(asc(schema.eventTickets.createdAt));
|
|
17
|
+
const allow = ctx.hasScope("events:pii");
|
|
18
|
+
const attendees = rows.map(({ ticket, userName, userEmail }) => redactPii({
|
|
19
|
+
...ticketToMajor(pick(ticket, ["id", "ticketTypeId", "status", "amount", "currency", "checkedInAt", "createdAt"])),
|
|
20
|
+
addons: ticket.addons.length,
|
|
21
|
+
userName,
|
|
22
|
+
userEmail
|
|
23
|
+
}, { allow, fields: ["userName", "userEmail"] }));
|
|
24
|
+
return { notice: piiNotice(allow, "events:pii", "Attendee names and emails"), attendees };
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -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
|
+
});
|