@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.
Files changed (104) hide show
  1. package/README.md +1 -1
  2. package/dist/module.d.mts +45 -7
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +53 -42
  5. package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +11 -0
  6. package/dist/runtime/app/components/EventAdminAddonDialog.vue +113 -0
  7. package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +11 -0
  8. package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +11 -0
  9. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +79 -0
  10. package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +11 -0
  11. package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +19 -0
  12. package/dist/runtime/app/components/EventAdminAddonGroups.vue +123 -0
  13. package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +19 -0
  14. package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +20 -0
  15. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +175 -0
  16. package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +20 -0
  17. package/dist/runtime/app/components/EventCard.vue +39 -27
  18. package/dist/runtime/app/components/EventTicketCard.vue +5 -4
  19. package/dist/runtime/app/components/EventTicketPicker.vue +40 -21
  20. package/dist/runtime/app/composables/index.d.ts +1 -1
  21. package/dist/runtime/app/composables/index.js +1 -1
  22. package/dist/runtime/app/composables/useEvents.d.ts +10 -4
  23. package/dist/runtime/app/composables/useEvents.js +21 -10
  24. package/dist/runtime/app/pages/admin-event.d.vue.ts +3 -0
  25. package/dist/runtime/app/pages/admin-event.vue +151 -0
  26. package/dist/runtime/app/pages/admin-event.vue.d.ts +3 -0
  27. package/dist/runtime/app/pages/admin.d.vue.ts +3 -0
  28. package/dist/runtime/app/pages/admin.vue +154 -0
  29. package/dist/runtime/app/pages/admin.vue.d.ts +3 -0
  30. package/dist/runtime/app/pages/event.d.vue.ts +14 -1
  31. package/dist/runtime/app/pages/event.vue +160 -45
  32. package/dist/runtime/app/pages/event.vue.d.ts +14 -1
  33. package/dist/runtime/app/pages/events.vue +12 -10
  34. package/dist/runtime/app/pages/tickets.vue +26 -19
  35. package/dist/runtime/app/tabs/Attendees.d.vue.ts +7 -0
  36. package/dist/runtime/app/tabs/Attendees.vue +88 -0
  37. package/dist/runtime/app/tabs/Attendees.vue.d.ts +7 -0
  38. package/dist/runtime/app/tabs/Details.d.vue.ts +11 -0
  39. package/dist/runtime/app/tabs/Details.vue +74 -0
  40. package/dist/runtime/app/tabs/Details.vue.d.ts +11 -0
  41. package/dist/runtime/app/tabs/Tickets.d.vue.ts +11 -0
  42. package/dist/runtime/app/tabs/Tickets.vue +164 -0
  43. package/dist/runtime/app/tabs/Tickets.vue.d.ts +11 -0
  44. package/dist/runtime/app/utils/admin-event.d.ts +48 -0
  45. package/dist/runtime/app/utils/admin-event.js +22 -0
  46. package/dist/runtime/i18n/lang/da.json +34 -1
  47. package/dist/runtime/i18n/lang/en.json +33 -0
  48. package/dist/runtime/menus.js +17 -1
  49. package/dist/runtime/server/db/schema.sqlite.d.ts +62 -5
  50. package/dist/runtime/server/db/schema.sqlite.js +27 -2
  51. package/dist/runtime/server/mcp/schemas.d.ts +119 -0
  52. package/dist/runtime/server/mcp/schemas.js +58 -0
  53. package/dist/runtime/server/mcp/tools/admin/create-event.d.ts +2 -0
  54. package/dist/runtime/server/mcp/tools/admin/create-event.js +11 -0
  55. package/dist/runtime/server/mcp/tools/admin/get-event-definition.d.ts +2 -0
  56. package/dist/runtime/server/mcp/tools/admin/get-event-definition.js +10 -0
  57. package/dist/runtime/server/mcp/tools/admin/list-all-events.d.ts +2 -0
  58. package/dist/runtime/server/mcp/tools/admin/list-all-events.js +9 -0
  59. package/dist/runtime/server/mcp/tools/admin/list-attendees.d.ts +2 -0
  60. package/dist/runtime/server/mcp/tools/admin/list-attendees.js +25 -0
  61. package/dist/runtime/server/mcp/tools/admin/update-event.d.ts +2 -0
  62. package/dist/runtime/server/mcp/tools/admin/update-event.js +10 -0
  63. package/dist/runtime/server/mcp/tools/admin/update-ticket-type.d.ts +2 -0
  64. package/dist/runtime/server/mcp/tools/admin/update-ticket-type.js +10 -0
  65. package/dist/runtime/server/mcp/tools/get-event.d.ts +2 -0
  66. package/dist/runtime/server/mcp/tools/get-event.js +12 -0
  67. package/dist/runtime/server/mcp/tools/list-events.d.ts +2 -0
  68. package/dist/runtime/server/mcp/tools/list-events.js +6 -0
  69. package/dist/runtime/server/mcp/tools/my-tickets.d.ts +2 -0
  70. package/dist/runtime/server/mcp/tools/my-tickets.js +6 -0
  71. package/dist/runtime/server/plugins/staff-scopes.d.ts +2 -0
  72. package/dist/runtime/server/plugins/staff-scopes.js +1 -0
  73. package/dist/runtime/server/plugins/subscriptions-membership.d.ts +2 -0
  74. package/dist/runtime/server/plugins/subscriptions-membership.js +11 -0
  75. package/dist/runtime/server/routes/admin/event-clone.post.d.ts +2 -0
  76. package/dist/runtime/server/routes/admin/event-clone.post.js +31 -0
  77. package/dist/runtime/server/routes/admin/event.get.d.ts +2 -0
  78. package/dist/runtime/server/routes/admin/event.get.js +5 -0
  79. package/dist/runtime/server/routes/admin/event.patch.js +3 -8
  80. package/dist/runtime/server/routes/admin/events.post.js +3 -13
  81. package/dist/runtime/server/routes/admin/membership-options.get.d.ts +2 -0
  82. package/dist/runtime/server/routes/admin/membership-options.get.js +4 -0
  83. package/dist/runtime/server/routes/admin/ticket-type.patch.js +3 -8
  84. package/dist/runtime/server/routes/admin/ticket-types.post.js +2 -1
  85. package/dist/runtime/server/routes/public/event.get.js +2 -2
  86. package/dist/runtime/server/routes/public/purchase.post.js +3 -2
  87. package/dist/runtime/server/routes/public/ticket-pay.post.js +1 -1
  88. package/dist/runtime/server/routes/public/ticket.get.js +1 -1
  89. package/dist/runtime/server/routes/public/tickets-me.get.js +1 -1
  90. package/dist/runtime/server/utils/admin-schemas.d.ts +102 -20
  91. package/dist/runtime/server/utils/admin-schemas.js +8 -2
  92. package/dist/runtime/server/utils/admin.d.ts +82 -1
  93. package/dist/runtime/server/utils/admin.js +51 -1
  94. package/dist/runtime/server/utils/auth.d.ts +7 -6
  95. package/dist/runtime/server/utils/auth.js +4 -4
  96. package/dist/runtime/server/utils/catalog.d.ts +14 -0
  97. package/dist/runtime/server/utils/catalog.js +20 -4
  98. package/dist/runtime/server/utils/membership.d.ts +6 -2
  99. package/dist/runtime/server/utils/membership.js +15 -2
  100. package/dist/runtime/server/utils/tickets.js +15 -12
  101. package/dist/runtime/utils/index.d.ts +17 -7
  102. package/dist/runtime/utils/index.js +16 -19
  103. package/dist/types.d.mts +3 -1
  104. 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
  }
@@ -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
- extendMenuItem("dashboard.account", defineMenuItem({
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: "string";
124
- columnType: "SQLiteText";
125
- data: string;
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: [string, ...string[]];
132
+ enumValues: undefined;
133
133
  baseColumn: never;
134
134
  identity: undefined;
135
135
  generated: undefined;
136
136
  }, {}, {
137
- length: undefined;
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
- location: text(),
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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,6 @@
1
+ export default defineScopedMcpTool({
2
+ description: "List the published events.",
3
+ scopes: ["tickets:read"],
4
+ annotations: { readOnlyHint: true },
5
+ handler: () => listPublishedEvents()
6
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,6 @@
1
+ export default defineScopedMcpTool({
2
+ description: "List your own tickets.",
3
+ scopes: ["tickets:read"],
4
+ annotations: { readOnlyHint: true },
5
+ handler: (_args, { user }) => listUserTickets(user.id)
6
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export default defineStaffScopes(["events:manage", "events:pii"], (event, userId) => canManageEvents(event, { user: { id: userId } }));
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { getEventWithTicketTypes } from "../../utils/admin.js";
2
+ export default defineEventHandler(async (event) => {
3
+ await requireEventsManager(event);
4
+ return getEventWithTicketTypes(getRouterParam(event, "id"));
5
+ });