@frigear-nu/events 0.0.7 → 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 +27 -0
- package/dist/runtime/i18n/lang/en.json +27 -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/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 +20 -7
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { eq } from "drizzle-orm";
|
|
3
|
-
import { eventInput } from "../../utils/admin-schemas.js";
|
|
4
|
-
const patchSchema = eventInput.omit({ ticketTypes: true }).partial();
|
|
1
|
+
import { eventPatchSchema, patchEventRow } from "../../utils/admin.js";
|
|
5
2
|
export default defineEventHandler(async (event) => {
|
|
6
3
|
await requireEventsManager(event);
|
|
7
|
-
const patch = await readValidatedBody(event,
|
|
8
|
-
|
|
9
|
-
if (!row) throw createError({ statusCode: 404, statusMessage: "Event not found." });
|
|
10
|
-
return row;
|
|
4
|
+
const patch = await readValidatedBody(event, eventPatchSchema.parse);
|
|
5
|
+
return patchEventRow(getRouterParam(event, "id"), patch);
|
|
11
6
|
});
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { db, schema } from "@nuxthub/db";
|
|
3
|
-
import { newId } from "../../../utils/index.js";
|
|
4
|
-
import { insertTicketType } from "../../utils/admin.js";
|
|
1
|
+
import { createEventWithCatalog } from "../../utils/admin.js";
|
|
5
2
|
import { eventInput } from "../../utils/admin-schemas.js";
|
|
6
3
|
export default defineEventHandler(async (event) => {
|
|
7
4
|
await requireEventsManager(event);
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
if (taken) throw createError({ statusCode: 409, statusMessage: "An event with this slug already exists." });
|
|
11
|
-
const [row] = await db.insert(schema.events).values({
|
|
12
|
-
id: newId("evn"),
|
|
13
|
-
...input,
|
|
14
|
-
requiresMembership: input.requiresMembership ?? null
|
|
15
|
-
}).returning();
|
|
16
|
-
for (const type of ticketTypes ?? []) await insertTicketType(row.id, type);
|
|
5
|
+
const input = await readValidatedBody(event, eventInput.parse);
|
|
6
|
+
const row = await createEventWithCatalog(input);
|
|
17
7
|
setResponseStatus(event, 201);
|
|
18
8
|
return row;
|
|
19
9
|
});
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { eq } from "drizzle-orm";
|
|
3
|
-
import { ticketTypeInput } from "../../utils/admin-schemas.js";
|
|
4
|
-
const patchSchema = ticketTypeInput.omit({ addonGroups: true }).partial();
|
|
1
|
+
import { patchTicketTypeRow, ticketTypePatchSchema } from "../../utils/admin.js";
|
|
5
2
|
export default defineEventHandler(async (event) => {
|
|
6
3
|
await requireEventsManager(event);
|
|
7
|
-
const patch = await readValidatedBody(event,
|
|
8
|
-
|
|
9
|
-
if (!row) throw createError({ statusCode: 404, statusMessage: "Ticket type not found." });
|
|
10
|
-
return row;
|
|
4
|
+
const patch = await readValidatedBody(event, ticketTypePatchSchema.parse);
|
|
5
|
+
return patchTicketTypeRow(getRouterParam(event, "id"), patch);
|
|
11
6
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { insertTicketType } from "../../utils/admin.js";
|
|
1
|
+
import { assertMembershipAvailable, insertTicketType } from "../../utils/admin.js";
|
|
2
2
|
import { ticketTypeInput } from "../../utils/admin-schemas.js";
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
4
|
await requireEventsManager(event);
|
|
5
5
|
const input = await readValidatedBody(event, ticketTypeInput.parse);
|
|
6
|
+
await assertMembershipAvailable(input);
|
|
6
7
|
const row = await insertTicketType(getRouterParam(event, "id"), input).catch((error) => {
|
|
7
8
|
throw createError({ statusCode: 409, statusMessage: "Could not create the ticket type (unknown event, or the key is taken).", cause: error });
|
|
8
9
|
});
|
|
@@ -2,6 +2,6 @@ export default defineEventHandler(async (event) => {
|
|
|
2
2
|
const slug = getRouterParam(event, "slug");
|
|
3
3
|
const row = await getPublishedEventRow(slug);
|
|
4
4
|
if (!row) throw createError({ statusCode: 404, statusMessage: "Event not found." });
|
|
5
|
-
const
|
|
6
|
-
return getEventDetail(row,
|
|
5
|
+
const user = await getUser(event, { scopes: ["tickets:read"] });
|
|
6
|
+
return getEventDetail(row, user?.id);
|
|
7
7
|
});
|
|
@@ -3,10 +3,11 @@ const bodySchema = z.object({
|
|
|
3
3
|
ticketTypeId: z.string().min(1),
|
|
4
4
|
addonIds: z.array(z.string().min(1)).max(50).optional(),
|
|
5
5
|
successUrl: z.string().optional(),
|
|
6
|
-
cancelUrl: z.string().optional()
|
|
6
|
+
cancelUrl: z.string().optional(),
|
|
7
|
+
locale: z.string().regex(/^[a-z]{2,3}(?:[-_][a-z0-9]{2,8})*$/i).max(16).optional()
|
|
7
8
|
});
|
|
8
9
|
export default defineEventHandler(async (event) => {
|
|
9
|
-
const
|
|
10
|
+
const user = await requireUser(event, { scopes: ["tickets:write"] });
|
|
10
11
|
const body = await readValidatedBody(event, bodySchema.parse);
|
|
11
12
|
return purchaseTicket(event, user.id, getRouterParam(event, "slug"), body);
|
|
12
13
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { and, eq } from "drizzle-orm";
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
const
|
|
4
|
+
const user = await requireUser(event, { scopes: ["tickets:write"] });
|
|
5
5
|
const [ticket] = await db.select().from(schema.eventTickets).where(and(eq(schema.eventTickets.id, getRouterParam(event, "id")), eq(schema.eventTickets.userId, user.id))).limit(1);
|
|
6
6
|
if (!ticket) throw createError({ statusCode: 404, statusMessage: "Ticket not found." });
|
|
7
7
|
if (ticket.status === "paid") throw createError({ statusCode: 409, statusMessage: "This ticket is already paid." });
|
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export declare const translated: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
2
|
+
export declare const translated: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
3
|
+
[k: string]: string;
|
|
4
|
+
}, Record<string, string>>>]>;
|
|
3
5
|
export declare const membership: z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>;
|
|
4
6
|
export declare const addonInput: z.ZodObject<{
|
|
5
|
-
label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
6
|
-
|
|
7
|
+
label: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
8
|
+
[k: string]: string;
|
|
9
|
+
}, Record<string, string>>>]>;
|
|
10
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
11
|
+
[k: string]: string;
|
|
12
|
+
}, Record<string, string>>>]>>>;
|
|
7
13
|
price: z.ZodOptional<z.ZodNumber>;
|
|
8
14
|
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
9
15
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
10
16
|
}, z.core.$strip>;
|
|
11
17
|
export declare const addonGroupInput: z.ZodObject<{
|
|
12
|
-
title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
18
|
+
title: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
19
|
+
[k: string]: string;
|
|
20
|
+
}, Record<string, string>>>]>;
|
|
13
21
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
14
22
|
multiple: z.ZodOptional<z.ZodBoolean>;
|
|
15
23
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
16
24
|
addons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
17
|
-
label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
18
|
-
|
|
25
|
+
label: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
26
|
+
[k: string]: string;
|
|
27
|
+
}, Record<string, string>>>]>;
|
|
28
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
29
|
+
[k: string]: string;
|
|
30
|
+
}, Record<string, string>>>]>>>;
|
|
19
31
|
price: z.ZodOptional<z.ZodNumber>;
|
|
20
32
|
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
21
33
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
@@ -23,26 +35,57 @@ export declare const addonGroupInput: z.ZodObject<{
|
|
|
23
35
|
}, z.core.$strip>;
|
|
24
36
|
export declare const ticketTypeInput: z.ZodObject<{
|
|
25
37
|
key: z.ZodString;
|
|
26
|
-
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
27
|
-
|
|
38
|
+
name: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
39
|
+
[k: string]: string;
|
|
40
|
+
}, Record<string, string>>>]>;
|
|
41
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
42
|
+
[k: string]: string;
|
|
43
|
+
}, Record<string, string>>>]>>>;
|
|
28
44
|
price: z.ZodOptional<z.ZodNumber>;
|
|
29
45
|
currency: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
30
46
|
hidePrice: z.ZodOptional<z.ZodBoolean>;
|
|
31
47
|
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
32
48
|
saleStartsAt: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
|
|
33
49
|
saleEndsAt: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
|
|
50
|
+
saleSchedule: z.ZodOptional<z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
51
|
+
type: z.ZodLiteral<"always">;
|
|
52
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
53
|
+
type: z.ZodLiteral<"range">;
|
|
54
|
+
from: z.ZodOptional<z.ZodString>;
|
|
55
|
+
to: z.ZodOptional<z.ZodString>;
|
|
56
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
57
|
+
type: z.ZodLiteral<"yearly">;
|
|
58
|
+
from: z.ZodObject<{
|
|
59
|
+
month: z.ZodNumber;
|
|
60
|
+
day: z.ZodNumber;
|
|
61
|
+
time: z.ZodString;
|
|
62
|
+
}, z.core.$strip>;
|
|
63
|
+
to: z.ZodObject<{
|
|
64
|
+
month: z.ZodNumber;
|
|
65
|
+
day: z.ZodNumber;
|
|
66
|
+
time: z.ZodString;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
timezone: z.ZodString;
|
|
69
|
+
}, z.core.$strip>], "type">>>;
|
|
34
70
|
maxPerUser: z.ZodOptional<z.ZodNumber>;
|
|
35
71
|
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
72
|
+
excludesMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
36
73
|
isDefault: z.ZodOptional<z.ZodBoolean>;
|
|
37
74
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
38
75
|
addonGroups: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
39
|
-
title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
76
|
+
title: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
77
|
+
[k: string]: string;
|
|
78
|
+
}, Record<string, string>>>]>;
|
|
40
79
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
41
80
|
multiple: z.ZodOptional<z.ZodBoolean>;
|
|
42
81
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
43
82
|
addons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
44
|
-
label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
45
|
-
|
|
83
|
+
label: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
84
|
+
[k: string]: string;
|
|
85
|
+
}, Record<string, string>>>]>;
|
|
86
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
87
|
+
[k: string]: string;
|
|
88
|
+
}, Record<string, string>>>]>>>;
|
|
46
89
|
price: z.ZodOptional<z.ZodNumber>;
|
|
47
90
|
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
48
91
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
@@ -51,11 +94,19 @@ export declare const ticketTypeInput: z.ZodObject<{
|
|
|
51
94
|
}, z.core.$strip>;
|
|
52
95
|
export declare const eventInput: z.ZodObject<{
|
|
53
96
|
slug: z.ZodString;
|
|
54
|
-
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
name: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
98
|
+
[k: string]: string;
|
|
99
|
+
}, Record<string, string>>>]>;
|
|
100
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
101
|
+
[k: string]: string;
|
|
102
|
+
}, Record<string, string>>>]>>>;
|
|
103
|
+
body: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
104
|
+
[k: string]: string;
|
|
105
|
+
}, Record<string, string>>>]>>>;
|
|
57
106
|
image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
58
|
-
location: z.ZodOptional<z.ZodNullable<z.ZodString
|
|
107
|
+
location: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
108
|
+
[k: string]: string;
|
|
109
|
+
}, Record<string, string>>>]>>>;
|
|
59
110
|
locationUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
60
111
|
startsAt: z.ZodCoercedDate<unknown>;
|
|
61
112
|
endsAt: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
|
|
@@ -67,26 +118,57 @@ export declare const eventInput: z.ZodObject<{
|
|
|
67
118
|
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
68
119
|
ticketTypes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
69
120
|
key: z.ZodString;
|
|
70
|
-
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
71
|
-
|
|
121
|
+
name: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
122
|
+
[k: string]: string;
|
|
123
|
+
}, Record<string, string>>>]>;
|
|
124
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
125
|
+
[k: string]: string;
|
|
126
|
+
}, Record<string, string>>>]>>>;
|
|
72
127
|
price: z.ZodOptional<z.ZodNumber>;
|
|
73
128
|
currency: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
74
129
|
hidePrice: z.ZodOptional<z.ZodBoolean>;
|
|
75
130
|
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
76
131
|
saleStartsAt: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
|
|
77
132
|
saleEndsAt: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
|
|
133
|
+
saleSchedule: z.ZodOptional<z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
134
|
+
type: z.ZodLiteral<"always">;
|
|
135
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
136
|
+
type: z.ZodLiteral<"range">;
|
|
137
|
+
from: z.ZodOptional<z.ZodString>;
|
|
138
|
+
to: z.ZodOptional<z.ZodString>;
|
|
139
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
140
|
+
type: z.ZodLiteral<"yearly">;
|
|
141
|
+
from: z.ZodObject<{
|
|
142
|
+
month: z.ZodNumber;
|
|
143
|
+
day: z.ZodNumber;
|
|
144
|
+
time: z.ZodString;
|
|
145
|
+
}, z.core.$strip>;
|
|
146
|
+
to: z.ZodObject<{
|
|
147
|
+
month: z.ZodNumber;
|
|
148
|
+
day: z.ZodNumber;
|
|
149
|
+
time: z.ZodString;
|
|
150
|
+
}, z.core.$strip>;
|
|
151
|
+
timezone: z.ZodString;
|
|
152
|
+
}, z.core.$strip>], "type">>>;
|
|
78
153
|
maxPerUser: z.ZodOptional<z.ZodNumber>;
|
|
79
154
|
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
155
|
+
excludesMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
80
156
|
isDefault: z.ZodOptional<z.ZodBoolean>;
|
|
81
157
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
82
158
|
addonGroups: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
83
|
-
title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
159
|
+
title: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
160
|
+
[k: string]: string;
|
|
161
|
+
}, Record<string, string>>>]>;
|
|
84
162
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
85
163
|
multiple: z.ZodOptional<z.ZodBoolean>;
|
|
86
164
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
87
165
|
addons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
88
|
-
label: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString
|
|
89
|
-
|
|
166
|
+
label: z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
167
|
+
[k: string]: string;
|
|
168
|
+
}, Record<string, string>>>]>;
|
|
169
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
170
|
+
[k: string]: string;
|
|
171
|
+
}, Record<string, string>>>]>>>;
|
|
90
172
|
price: z.ZodOptional<z.ZodNumber>;
|
|
91
173
|
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
92
174
|
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
2
|
+
import { scheduleSchema } from "@frigear-nu/module-kit/schedule";
|
|
3
|
+
export const translated = z.union([
|
|
4
|
+
z.string().min(1),
|
|
5
|
+
z.record(z.string(), z.string()).transform((value) => Object.fromEntries(Object.entries(value).filter(([, text]) => text.trim()))).refine((value) => Object.keys(value).length > 0, "Add a text in at least one language.")
|
|
6
|
+
]);
|
|
3
7
|
export const membership = z.union([z.literal("any"), z.array(z.string().min(1))]).nullable();
|
|
4
8
|
const date = z.coerce.date();
|
|
5
9
|
export const addonInput = z.object({
|
|
@@ -26,8 +30,10 @@ export const ticketTypeInput = z.object({
|
|
|
26
30
|
capacity: z.number().int().min(0).nullish(),
|
|
27
31
|
saleStartsAt: date.nullish(),
|
|
28
32
|
saleEndsAt: date.nullish(),
|
|
33
|
+
saleSchedule: scheduleSchema.nullish(),
|
|
29
34
|
maxPerUser: z.number().int().min(1).optional(),
|
|
30
35
|
requiresMembership: membership.optional(),
|
|
36
|
+
excludesMembership: membership.optional(),
|
|
31
37
|
isDefault: z.boolean().optional(),
|
|
32
38
|
sortOrder: z.number().int().optional(),
|
|
33
39
|
addonGroups: z.array(addonGroupInput).optional()
|
|
@@ -38,7 +44,7 @@ export const eventInput = z.object({
|
|
|
38
44
|
description: translated.nullish(),
|
|
39
45
|
body: translated.nullish(),
|
|
40
46
|
image: z.string().nullish(),
|
|
41
|
-
location:
|
|
47
|
+
location: translated.nullish(),
|
|
42
48
|
locationUrl: z.string().nullish(),
|
|
43
49
|
startsAt: date,
|
|
44
50
|
endsAt: date.nullish(),
|
|
@@ -1,7 +1,88 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
-
import
|
|
2
|
+
import { eventInput, ticketTypeInput } from './admin-schemas.js';
|
|
3
|
+
import type { addonGroupInput } from './admin-schemas.js';
|
|
3
4
|
type TicketTypeInput = z.output<typeof ticketTypeInput>;
|
|
4
5
|
type AddonGroupInput = z.output<typeof addonGroupInput>;
|
|
5
6
|
export declare function insertAddonGroup(ticketTypeId: string, input: AddonGroupInput): Promise<any>;
|
|
6
7
|
export declare function insertTicketType(eventId: string, input: TicketTypeInput): Promise<any>;
|
|
8
|
+
/**
|
|
9
|
+
* Membership rules only make sense with a membership provider (`@frigear-nu/subscriptions`): without one a
|
|
10
|
+
* requirement could never be met (and would silently lock the ticket), so staff can't set one.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertMembershipAvailable(...inputs: Array<{
|
|
13
|
+
requiresMembership?: unknown;
|
|
14
|
+
excludesMembership?: unknown;
|
|
15
|
+
} | undefined>): Promise<void>;
|
|
16
|
+
type EventInput = z.output<typeof eventInput>;
|
|
17
|
+
/** Creates an event, optionally with its whole catalogue (ticket types -> addon groups -> addons). */
|
|
18
|
+
export declare function createEventWithCatalog(raw: EventInput): Promise<any>;
|
|
19
|
+
export declare const eventPatchSchema: z.ZodObject<{
|
|
20
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
21
|
+
[k: string]: string;
|
|
22
|
+
}, Record<string, string>>>]>>;
|
|
23
|
+
status: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
|
|
24
|
+
draft: "draft";
|
|
25
|
+
published: "published";
|
|
26
|
+
archived: "archived";
|
|
27
|
+
}>>>;
|
|
28
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
29
|
+
description: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
30
|
+
[k: string]: string;
|
|
31
|
+
}, Record<string, string>>>]>>>>;
|
|
32
|
+
body: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
33
|
+
[k: string]: string;
|
|
34
|
+
}, Record<string, string>>>]>>>>;
|
|
35
|
+
image: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
36
|
+
location: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
37
|
+
[k: string]: string;
|
|
38
|
+
}, Record<string, string>>>]>>>>;
|
|
39
|
+
locationUrl: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
40
|
+
startsAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
41
|
+
endsAt: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
42
|
+
requiresMembership: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export declare function patchEventRow(id: string, patch: z.output<typeof eventPatchSchema>): Promise<any>;
|
|
45
|
+
export declare const ticketTypePatchSchema: z.ZodObject<{
|
|
46
|
+
currency: z.ZodOptional<z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
47
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
48
|
+
[k: string]: string;
|
|
49
|
+
}, Record<string, string>>>]>>;
|
|
50
|
+
key: z.ZodOptional<z.ZodString>;
|
|
51
|
+
price: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
52
|
+
hidePrice: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
53
|
+
description: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodString>, z.ZodTransform<{
|
|
54
|
+
[k: string]: string;
|
|
55
|
+
}, Record<string, string>>>]>>>>;
|
|
56
|
+
requiresMembership: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>>;
|
|
57
|
+
capacity: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
58
|
+
saleStartsAt: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
59
|
+
saleEndsAt: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>>;
|
|
60
|
+
saleSchedule: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
61
|
+
type: z.ZodLiteral<"always">;
|
|
62
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
63
|
+
type: z.ZodLiteral<"range">;
|
|
64
|
+
from: z.ZodOptional<z.ZodString>;
|
|
65
|
+
to: z.ZodOptional<z.ZodString>;
|
|
66
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
67
|
+
type: z.ZodLiteral<"yearly">;
|
|
68
|
+
from: z.ZodObject<{
|
|
69
|
+
month: z.ZodNumber;
|
|
70
|
+
day: z.ZodNumber;
|
|
71
|
+
time: z.ZodString;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
to: z.ZodObject<{
|
|
74
|
+
month: z.ZodNumber;
|
|
75
|
+
day: z.ZodNumber;
|
|
76
|
+
time: z.ZodString;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
timezone: z.ZodString;
|
|
79
|
+
}, z.core.$strip>], "type">>>>;
|
|
80
|
+
maxPerUser: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
81
|
+
excludesMembership: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>>;
|
|
82
|
+
isDefault: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
83
|
+
sortOrder: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
84
|
+
}, z.core.$strip>;
|
|
85
|
+
export declare function patchTicketTypeRow(id: string, patch: z.output<typeof ticketTypePatchSchema>): Promise<any>;
|
|
86
|
+
/** One event with all of its ticket types (capacity and sale window included), whatever its status. */
|
|
87
|
+
export declare function getEventWithTicketTypes(id: string): Promise<any>;
|
|
7
88
|
export {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { newId } from "../../utils/index.js";
|
|
3
|
+
import { asc, eq } from "drizzle-orm";
|
|
4
|
+
import { loadCatalog } from "./catalog.js";
|
|
5
|
+
import { eventInput, ticketTypeInput } from "./admin-schemas.js";
|
|
3
6
|
export async function insertAddonGroup(ticketTypeId, input) {
|
|
4
7
|
const { addons, ...group } = input;
|
|
5
8
|
const [row] = await db.insert(schema.eventAddonGroups).values({ id: newId("agr"), ticketTypeId, ...group }).returning();
|
|
@@ -17,8 +20,55 @@ export async function insertTicketType(eventId, input) {
|
|
|
17
20
|
capacity: type.capacity ?? null,
|
|
18
21
|
saleStartsAt: type.saleStartsAt ?? null,
|
|
19
22
|
saleEndsAt: type.saleEndsAt ?? null,
|
|
20
|
-
|
|
23
|
+
saleSchedule: type.saleSchedule ?? null,
|
|
24
|
+
requiresMembership: type.requiresMembership ?? null,
|
|
25
|
+
excludesMembership: type.excludesMembership ?? null
|
|
21
26
|
}).returning();
|
|
22
27
|
for (const group of addonGroups ?? []) await insertAddonGroup(row.id, group);
|
|
23
28
|
return row;
|
|
24
29
|
}
|
|
30
|
+
export async function assertMembershipAvailable(...inputs) {
|
|
31
|
+
if (!inputs.some((input) => input?.requiresMembership || input?.excludesMembership)) return;
|
|
32
|
+
if ((await getMembershipOptions()).available) return;
|
|
33
|
+
throw createError({ statusCode: 400, statusMessage: "Membership rules need a membership provider such as @frigear-nu/subscriptions." });
|
|
34
|
+
}
|
|
35
|
+
export async function createEventWithCatalog(raw) {
|
|
36
|
+
const { ticketTypes, ...input } = raw;
|
|
37
|
+
await assertMembershipAvailable(input, ...ticketTypes ?? []);
|
|
38
|
+
const [taken] = await db.select({ id: schema.events.id }).from(schema.events).where(eq(schema.events.slug, input.slug)).limit(1);
|
|
39
|
+
if (taken) throw createError({ statusCode: 409, statusMessage: "An event with this slug already exists." });
|
|
40
|
+
const [row] = await db.insert(schema.events).values({
|
|
41
|
+
id: newId("evn"),
|
|
42
|
+
...input,
|
|
43
|
+
requiresMembership: input.requiresMembership ?? null
|
|
44
|
+
}).returning();
|
|
45
|
+
for (const type of ticketTypes ?? []) await insertTicketType(row.id, type);
|
|
46
|
+
return row;
|
|
47
|
+
}
|
|
48
|
+
export const eventPatchSchema = eventInput.omit({ ticketTypes: true }).partial();
|
|
49
|
+
export async function patchEventRow(id, patch) {
|
|
50
|
+
await assertMembershipAvailable(patch);
|
|
51
|
+
const [row] = await db.update(schema.events).set(patch).where(eq(schema.events.id, id)).returning();
|
|
52
|
+
if (!row) throw createError({ statusCode: 404, statusMessage: "Event not found." });
|
|
53
|
+
return row;
|
|
54
|
+
}
|
|
55
|
+
export const ticketTypePatchSchema = ticketTypeInput.omit({ addonGroups: true }).partial();
|
|
56
|
+
export async function patchTicketTypeRow(id, patch) {
|
|
57
|
+
await assertMembershipAvailable(patch);
|
|
58
|
+
const [row] = await db.update(schema.eventTicketTypes).set(patch).where(eq(schema.eventTicketTypes.id, id)).returning();
|
|
59
|
+
if (!row) throw createError({ statusCode: 404, statusMessage: "Ticket type not found." });
|
|
60
|
+
return row;
|
|
61
|
+
}
|
|
62
|
+
export async function getEventWithTicketTypes(id) {
|
|
63
|
+
const [row] = await db.select().from(schema.events).where(eq(schema.events.id, id)).limit(1);
|
|
64
|
+
if (!row) throw createError({ statusCode: 404, statusMessage: "Event not found." });
|
|
65
|
+
const ticketTypes = await db.select().from(schema.eventTicketTypes).where(eq(schema.eventTicketTypes.eventId, id)).orderBy(asc(schema.eventTicketTypes.sortOrder));
|
|
66
|
+
const { groups, addons } = await loadCatalog(id);
|
|
67
|
+
return {
|
|
68
|
+
...row,
|
|
69
|
+
ticketTypes: ticketTypes.map((type) => ({
|
|
70
|
+
...type,
|
|
71
|
+
addonGroups: groups.filter((group) => group.ticketTypeId === type.id).map((group) => ({ ...group, addons: addons.filter((addon) => addon.groupId === group.id) }))
|
|
72
|
+
}))
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
|
-
/**
|
|
3
|
-
* Gate for staff routes (event/ticket management, check-in). This repo has no shared permission
|
|
4
|
-
* system, so the host app decides through the `events:authorize` Nitro hook. Denied by default:
|
|
5
|
-
*
|
|
6
|
-
* nitroApp.hooks.hook('events:authorize', (ctx) => { ctx.allowed = isStaff(ctx.userId) })
|
|
7
|
-
*/
|
|
2
|
+
/** Gate for staff routes (event/ticket management, check-in). */
|
|
8
3
|
export declare function requireEventsManager(event: H3Event): Promise<{
|
|
9
4
|
userId: number;
|
|
10
5
|
}>;
|
|
6
|
+
/** Non-throwing form of the `events:authorize` policy for the caller of an already-authenticated request (MCP). */
|
|
7
|
+
export declare const canManageEvents: (event: H3Event, principal: {
|
|
8
|
+
user: {
|
|
9
|
+
id: number;
|
|
10
|
+
};
|
|
11
|
+
}) => any;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
const gate = definePolicyGate("events:authorize");
|
|
1
2
|
export async function requireEventsManager(event) {
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
await useNitroApp().hooks.callHook("events:authorize", ctx);
|
|
5
|
-
if (!ctx.allowed) throw createError({ statusCode: 403, statusMessage: "You are not allowed to manage events." });
|
|
3
|
+
const user = await requireUser(event, { scopes: ["events:manage"] });
|
|
4
|
+
await gate.assert(event, user.id, "You are not allowed to manage events.");
|
|
6
5
|
return { userId: user.id };
|
|
7
6
|
}
|
|
7
|
+
export const canManageEvents = (event, principal) => gate.isAllowed(event, principal.user.id);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { schema } from '@nuxthub/db';
|
|
2
2
|
import type { EventDetailDto, EventSummaryDto, TicketDto } from '../../../types.js';
|
|
3
|
+
import type { Schedule } from '@frigear-nu/module-kit/schedule';
|
|
3
4
|
type EventRow = typeof schema.events.$inferSelect;
|
|
4
5
|
type TicketRow = typeof schema.eventTickets.$inferSelect;
|
|
5
6
|
/** Tickets that currently occupy a seat: paid ones, and unpaid ones still inside their reservation window. */
|
|
@@ -15,6 +16,19 @@ export declare function loadCatalog(eventId: string): Promise<{
|
|
|
15
16
|
groups: any;
|
|
16
17
|
addons: any;
|
|
17
18
|
}>;
|
|
19
|
+
type SaleWindowType = {
|
|
20
|
+
saleStartsAt: Date | null;
|
|
21
|
+
saleEndsAt: Date | null;
|
|
22
|
+
saleSchedule: Schedule | null;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Whether a ticket type can be bought at `now`: its fixed window (`saleStartsAt`/`saleEndsAt`) and its optional
|
|
26
|
+
* recurring `saleSchedule` must both be open. `opensAt` is when sales (re)open, if they will.
|
|
27
|
+
*/
|
|
28
|
+
export declare function saleWindow(type: SaleWindowType, now?: Date): {
|
|
29
|
+
state: 'open' | 'not_started' | 'ended';
|
|
30
|
+
opensAt: Date | null;
|
|
31
|
+
};
|
|
18
32
|
/**
|
|
19
33
|
* The public event detail. For a signed-in user (`userId`) each ticket type also says whether they
|
|
20
34
|
* may buy it (`blocked`), so the UI can disable it with the right badge.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { and, asc, eq, gt, inArray, like, or, sql } from "drizzle-orm";
|
|
3
|
+
import { isScheduleOpen, nextScheduleOpening } from "@frigear-nu/module-kit/schedule";
|
|
3
4
|
import { normalizeMembership } from "../../utils/index.js";
|
|
4
|
-
import { meetsMembership } from "./membership.js";
|
|
5
|
+
import { meetsMembership, passesExclusion } from "./membership.js";
|
|
5
6
|
export function holdsSeat() {
|
|
6
7
|
const cutoff = new Date(Date.now() - useRuntimeConfig().events.reservationMinutes * 6e4);
|
|
7
8
|
return or(
|
|
@@ -46,6 +47,16 @@ export async function loadCatalog(eventId) {
|
|
|
46
47
|
const addons = groupIds.length ? await db.select().from(schema.eventAddons).where(inArray(schema.eventAddons.groupId, groupIds)).orderBy(asc(schema.eventAddons.sortOrder)) : [];
|
|
47
48
|
return { types, groups, addons };
|
|
48
49
|
}
|
|
50
|
+
export function saleWindow(type, now = /* @__PURE__ */ new Date()) {
|
|
51
|
+
const time = now.getTime();
|
|
52
|
+
if (type.saleStartsAt && type.saleStartsAt.getTime() > time) return { state: "not_started", opensAt: type.saleStartsAt };
|
|
53
|
+
if (type.saleEndsAt && type.saleEndsAt.getTime() < time) return { state: "ended", opensAt: null };
|
|
54
|
+
if (!isScheduleOpen(type.saleSchedule ?? void 0, now)) {
|
|
55
|
+
const opensAt = nextScheduleOpening(type.saleSchedule ?? void 0, now);
|
|
56
|
+
return opensAt && (!type.saleEndsAt || opensAt < type.saleEndsAt) ? { state: "not_started", opensAt } : { state: "ended", opensAt: null };
|
|
57
|
+
}
|
|
58
|
+
return { state: "open", opensAt: null };
|
|
59
|
+
}
|
|
49
60
|
export async function getEventDetail(row, userId) {
|
|
50
61
|
const { types, groups, addons } = await loadCatalog(row.id);
|
|
51
62
|
const eventRequirement = normalizeMembership(row.requiresMembership);
|
|
@@ -53,11 +64,14 @@ export async function getEventDetail(row, userId) {
|
|
|
53
64
|
const ticketTypes = await Promise.all(types.map(async (type) => {
|
|
54
65
|
const seats = type.capacity === null ? null : await countSeats(type.id);
|
|
55
66
|
const remaining = type.capacity === null ? null : Math.max(type.capacity - (seats ?? 0), 0);
|
|
56
|
-
const
|
|
67
|
+
const window = saleWindow(type, new Date(now));
|
|
68
|
+
const saleState = window.state === "open" && remaining === 0 ? "sold_out" : window.state;
|
|
57
69
|
const typeRequirement = normalizeMembership(type.requiresMembership);
|
|
70
|
+
const typeExclusion = normalizeMembership(type.excludesMembership);
|
|
58
71
|
let blocked = null;
|
|
59
72
|
if (userId !== void 0) {
|
|
60
73
|
if (!await meetsMembership(userId, eventRequirement) || !await meetsMembership(userId, typeRequirement)) blocked = "membership_required";
|
|
74
|
+
else if (!await passesExclusion(userId, typeExclusion)) blocked = "membership_excluded";
|
|
61
75
|
else {
|
|
62
76
|
const [owned] = await db.select({ count: sql`count(*)` }).from(schema.eventTickets).where(and(
|
|
63
77
|
eq(schema.eventTickets.userId, userId),
|
|
@@ -90,8 +104,9 @@ export async function getEventDetail(row, userId) {
|
|
|
90
104
|
hidePrice: type.hidePrice,
|
|
91
105
|
isDefault: type.isDefault,
|
|
92
106
|
requiresMembership: typeRequirement,
|
|
107
|
+
excludesMembership: typeExclusion,
|
|
93
108
|
remaining,
|
|
94
|
-
saleStartsAt: type.saleStartsAt?.toISOString() ?? null,
|
|
109
|
+
saleStartsAt: (window.state === "not_started" ? window.opensAt : type.saleStartsAt)?.toISOString() ?? null,
|
|
95
110
|
saleEndsAt: type.saleEndsAt?.toISOString() ?? null,
|
|
96
111
|
saleState,
|
|
97
112
|
blocked,
|
|
@@ -123,7 +138,8 @@ export async function toTicketDtos(rows) {
|
|
|
123
138
|
paymentId: row.paymentId,
|
|
124
139
|
code: row.status === "paid" ? row.code : null,
|
|
125
140
|
checkedInAt: row.checkedInAt?.toISOString() ?? null,
|
|
126
|
-
createdAt: row.createdAt.toISOString()
|
|
141
|
+
createdAt: row.createdAt.toISOString(),
|
|
142
|
+
locale: row.locale ?? null
|
|
127
143
|
};
|
|
128
144
|
});
|
|
129
145
|
}
|