@frigear-nu/events 0.1.2 → 0.2.0
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/dist/module.d.mts +105 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +35 -31
- package/dist/runtime/app/components/EventAddonGroupSlideover.d.vue.ts +18 -0
- package/dist/runtime/app/components/EventAddonGroupSlideover.vue +67 -0
- package/dist/runtime/app/components/EventAddonGroupSlideover.vue.d.ts +18 -0
- package/dist/runtime/app/components/EventAddonSlideover.d.vue.ts +20 -0
- package/dist/runtime/app/components/EventAddonSlideover.vue +104 -0
- package/dist/runtime/app/components/EventAddonSlideover.vue.d.ts +20 -0
- package/dist/runtime/app/components/EventAdminAddonGroups.d.vue.ts +1 -9
- package/dist/runtime/app/components/EventAdminAddonGroups.vue +7 -48
- package/dist/runtime/app/components/EventAdminAddonGroups.vue.d.ts +1 -9
- package/dist/runtime/app/components/EventCard.vue +1 -0
- package/dist/runtime/app/components/EventFormSlideover.vue +46 -23
- package/dist/runtime/app/components/EventTicketCard.vue +60 -62
- package/dist/runtime/app/components/EventTicketPicker.vue +1 -0
- package/dist/runtime/app/components/EventTicketTypeSlideover.d.vue.ts +20 -0
- package/dist/runtime/app/components/EventTicketTypeSlideover.vue +177 -0
- package/dist/runtime/app/components/EventTicketTypeSlideover.vue.d.ts +20 -0
- package/dist/runtime/app/composables/index.d.ts +2 -0
- package/dist/runtime/app/composables/index.js +1 -0
- package/dist/runtime/app/composables/useEventActions.d.ts +3 -3
- package/dist/runtime/app/composables/useEventActions.js +10 -4
- package/dist/runtime/app/composables/useEventCatalogActions.d.ts +33 -0
- package/dist/runtime/app/composables/useEventCatalogActions.js +108 -0
- package/dist/runtime/app/composables/useEventLinks.js +1 -0
- package/dist/runtime/app/composables/useEvents.d.ts +29 -16
- package/dist/runtime/app/composables/useEvents.js +7 -6
- package/dist/runtime/app/pages/admin-event.vue +72 -136
- package/dist/runtime/app/pages/admin.vue +92 -29
- package/dist/runtime/app/pages/event.vue +9 -4
- package/dist/runtime/app/pages/events.vue +1 -0
- package/dist/runtime/app/pages/tickets.vue +5 -0
- package/dist/runtime/app/tabs/Attendees.vue +79 -39
- package/dist/runtime/app/tabs/Details.d.vue.ts +1 -1
- package/dist/runtime/app/tabs/Details.vue +113 -30
- package/dist/runtime/app/tabs/Details.vue.d.ts +1 -1
- package/dist/runtime/app/tabs/Tickets.vue +103 -139
- package/dist/runtime/app/utils/admin-event.d.ts +53 -38
- package/dist/runtime/app/utils/admin-event.js +14 -0
- package/dist/runtime/app/widgets/UpcomingEvents.vue +3 -1
- package/dist/runtime/app/widgets/UpcomingEventsStat.vue +3 -1
- package/dist/runtime/i18n/lang/da.json +22 -3
- package/dist/runtime/i18n/lang/en.json +22 -3
- package/dist/runtime/menus.js +1 -0
- package/dist/runtime/server/bootstrap/my-tickets.d.ts +1 -1
- package/dist/runtime/server/mcp/schemas.d.ts +4 -4
- package/dist/runtime/server/routes/admin/addon-group.delete.js +6 -3
- package/dist/runtime/server/routes/admin/addon-group.patch.js +6 -4
- package/dist/runtime/server/routes/admin/addon-groups.post.js +8 -6
- package/dist/runtime/server/routes/admin/addon.delete.js +6 -3
- package/dist/runtime/server/routes/admin/addon.patch.js +8 -5
- package/dist/runtime/server/routes/admin/addons.post.js +8 -6
- package/dist/runtime/server/routes/admin/attendees.get.js +38 -8
- package/dist/runtime/server/routes/{public → admin}/check-in.post.js +4 -3
- package/dist/runtime/server/routes/admin/event-clone.post.js +37 -19
- package/dist/runtime/server/routes/admin/event.delete.js +7 -4
- package/dist/runtime/server/routes/admin/event.get.js +4 -2
- package/dist/runtime/server/routes/admin/event.patch.js +4 -2
- package/dist/runtime/server/routes/admin/events.get.js +36 -3
- package/dist/runtime/server/routes/admin/events.post.js +3 -4
- package/dist/runtime/server/routes/admin/membership-options.get.js +3 -2
- package/dist/runtime/server/routes/admin/ticket-cancel.post.js +7 -8
- package/dist/runtime/server/routes/admin/ticket-refund.post.js +7 -5
- package/dist/runtime/server/routes/admin/ticket-type.delete.js +7 -4
- package/dist/runtime/server/routes/admin/ticket-type.patch.js +4 -2
- package/dist/runtime/server/routes/admin/ticket-types.post.js +10 -7
- package/dist/runtime/server/routes/admin/upcoming.get.js +7 -3
- package/dist/runtime/server/routes/me/ticket-pay.post.js +13 -0
- package/dist/runtime/server/routes/me/ticket.get.js +6 -0
- package/dist/runtime/server/routes/public/event.get.js +5 -2
- package/dist/runtime/server/routes/public/purchase.post.js +3 -1
- package/dist/runtime/server/utils/admin-dto.d.ts +30 -0
- package/dist/runtime/server/utils/admin-dto.js +101 -0
- package/dist/runtime/server/utils/admin-schemas.js +2 -1
- package/dist/runtime/server/utils/admin.d.ts +10 -8
- package/dist/runtime/server/utils/admin.js +20 -6
- package/dist/runtime/server/utils/auth.d.ts +5 -4
- package/dist/runtime/server/utils/auth.js +6 -7
- package/dist/runtime/server/utils/tickets.js +6 -11
- package/dist/runtime/utils/api.d.ts +48 -0
- package/dist/runtime/utils/api.js +51 -0
- package/dist/runtime/utils/index.d.ts +2 -1
- package/dist/runtime/utils/index.js +1 -0
- package/dist/types.d.mts +1 -1
- package/package.json +19 -14
- package/dist/runtime/app/components/EventAdminAddonDialog.d.vue.ts +0 -11
- package/dist/runtime/app/components/EventAdminAddonDialog.vue +0 -112
- package/dist/runtime/app/components/EventAdminAddonDialog.vue.d.ts +0 -11
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.d.vue.ts +0 -11
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue +0 -78
- package/dist/runtime/app/components/EventAdminAddonGroupDialog.vue.d.ts +0 -11
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.d.vue.ts +0 -20
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue +0 -174
- package/dist/runtime/app/components/EventAdminTicketTypeDialog.vue.d.ts +0 -20
- package/dist/runtime/server/routes/public/ticket-pay.post.js +0 -10
- package/dist/runtime/server/routes/public/ticket.get.js +0 -4
- /package/dist/runtime/server/routes/{public → admin}/check-in.post.d.ts +0 -0
- /package/dist/runtime/server/routes/{public → me}/ticket-pay.post.d.ts +0 -0
- /package/dist/runtime/server/routes/{public → me}/ticket.get.d.ts +0 -0
- /package/dist/runtime/server/routes/{public/tickets-me.get.d.ts → me/tickets.get.d.ts} +0 -0
- /package/dist/runtime/server/routes/{public/tickets-me.get.js → me/tickets.get.js} +0 -0
|
@@ -1,45 +1,60 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
description: Translated | null;
|
|
6
|
-
price: number;
|
|
7
|
-
capacity: number | null;
|
|
8
|
-
};
|
|
9
|
-
export type StaffAddonGroup = {
|
|
10
|
-
id: string;
|
|
11
|
-
title: Translated;
|
|
12
|
-
required: boolean;
|
|
13
|
-
multiple: boolean;
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AdminAddonDto, AdminAddonGroupDto, AdminEventDetailDto, AdminEventDto, AdminTicketTypeDto, AttendeeDto, Translated } from '../../../types.js';
|
|
3
|
+
export type StaffAddon = AdminAddonDto;
|
|
4
|
+
export type StaffAddonGroup = AdminAddonGroupDto & {
|
|
14
5
|
addons: StaffAddon[];
|
|
15
6
|
};
|
|
16
|
-
export type StaffTicketType = {
|
|
17
|
-
id: string;
|
|
18
|
-
key: string;
|
|
19
|
-
name: Translated;
|
|
20
|
-
description: Translated | null;
|
|
21
|
-
price: number;
|
|
22
|
-
currency: string;
|
|
23
|
-
capacity: number | null;
|
|
24
|
-
saleStartsAt: string | null;
|
|
25
|
-
saleEndsAt: string | null;
|
|
26
|
-
requiresMembership: 'any' | string[] | null;
|
|
27
|
-
excludesMembership: 'any' | string[] | null;
|
|
7
|
+
export type StaffTicketType = AdminTicketTypeDto & {
|
|
28
8
|
addonGroups: StaffAddonGroup[];
|
|
29
9
|
};
|
|
30
|
-
export type StaffEvent =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
10
|
+
export type StaffEvent = AdminEventDetailDto;
|
|
11
|
+
/** The fields the event list shows (`GET /api/admin/events` items); the full `StaffEvent` fits it too. */
|
|
12
|
+
export type StaffEventSummary = Pick<AdminEventDto, 'id' | 'slug' | 'name' | 'status' | 'startsAt' | 'location'>;
|
|
13
|
+
/** One page of `GET /api/admin/events`. */
|
|
14
|
+
export type StaffEventsPage = {
|
|
15
|
+
items: AdminEventDto[];
|
|
16
|
+
total: number;
|
|
17
|
+
page: number;
|
|
18
|
+
pageSize: number;
|
|
19
|
+
};
|
|
20
|
+
/** One page of `GET /api/admin/events/:id/tickets`. */
|
|
21
|
+
export type AttendeesPage = {
|
|
22
|
+
items: AttendeeDto[];
|
|
23
|
+
total: number;
|
|
24
|
+
page: number;
|
|
25
|
+
pageSize: number;
|
|
41
26
|
};
|
|
42
|
-
/** The fields the event list shows; the full `StaffEvent` fits it too. */
|
|
43
|
-
export type StaffEventSummary = Pick<StaffEvent, 'id' | 'slug' | 'name' | 'status' | 'startsAt' | 'location'>;
|
|
44
27
|
/** `datetime-local` inputs want the local time without a zone, not the stored ISO instant. */
|
|
45
28
|
export declare const toLocalInput: (value: string | null) => string;
|
|
29
|
+
export declare const EVENT_STATUSES: readonly ["draft", "published", "archived"];
|
|
30
|
+
export type EventStatus = typeof EVENT_STATUSES[number];
|
|
31
|
+
type Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string;
|
|
32
|
+
type TranslateText = (value: Translated | null | undefined) => string;
|
|
33
|
+
/**
|
|
34
|
+
* The event form, shared by the create slideover (`EventFormSlideover`) and the "Details" tab so both validate the
|
|
35
|
+
* same way. `t` is `useModuleT('events')`, `tr` is `useEventsTranslate()`. Dates are `datetime-local` strings.
|
|
36
|
+
*/
|
|
37
|
+
export declare function eventFormSchema(t: Translate, tr: TranslateText): z.ZodObject<{
|
|
38
|
+
name: z.ZodCustom<Translated | undefined, Translated | undefined>;
|
|
39
|
+
slug: z.ZodString;
|
|
40
|
+
startsAt: z.ZodString;
|
|
41
|
+
endsAt: z.ZodOptional<z.ZodString>;
|
|
42
|
+
status: z.ZodEnum<{
|
|
43
|
+
draft: "draft";
|
|
44
|
+
published: "published";
|
|
45
|
+
archived: "archived";
|
|
46
|
+
}>;
|
|
47
|
+
location: z.ZodOptional<z.ZodCustom<Translated | undefined, Translated | undefined>>;
|
|
48
|
+
description: z.ZodOptional<z.ZodCustom<Translated | undefined, Translated | undefined>>;
|
|
49
|
+
body: z.ZodOptional<z.ZodCustom<Translated | undefined, Translated | undefined>>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export type EventFormState = z.output<ReturnType<typeof eventFormSchema>>;
|
|
52
|
+
/** The membership plans a ticket type can require or exclude (`@frigear-nu/subscriptions`), for the ticket type form. */
|
|
53
|
+
export type MembershipPlans = {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
plans: Array<{
|
|
56
|
+
label: string;
|
|
57
|
+
value: string;
|
|
58
|
+
}>;
|
|
59
|
+
};
|
|
60
|
+
export {};
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
export const toLocalInput = (value) => {
|
|
2
3
|
if (!value) return "";
|
|
3
4
|
const date = new Date(value);
|
|
4
5
|
return new Date(date.getTime() - date.getTimezoneOffset() * 6e4).toISOString().slice(0, 16);
|
|
5
6
|
};
|
|
7
|
+
export const EVENT_STATUSES = ["draft", "published", "archived"];
|
|
8
|
+
export function eventFormSchema(t, tr) {
|
|
9
|
+
return z.object({
|
|
10
|
+
name: z.custom().refine((value) => !!tr(value).trim(), t("admin.nameRequired", "Enter a name.")),
|
|
11
|
+
slug: z.string().trim().min(1, t("admin.slugRequired", "Enter a URL name.")).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, t("admin.slugInvalid", "Use lowercase letters, numbers and dashes.")),
|
|
12
|
+
startsAt: z.string().min(1, t("admin.startsRequired", "Enter when the event starts.")),
|
|
13
|
+
endsAt: z.string().optional(),
|
|
14
|
+
status: z.enum(EVENT_STATUSES),
|
|
15
|
+
location: z.custom().optional(),
|
|
16
|
+
description: z.custom().optional(),
|
|
17
|
+
body: z.custom().optional()
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { eventsApi } from "../../utils/api";
|
|
3
|
+
import { useEventLinks, useEventsTranslate, useFormat, useLazyFetch, useModuleT } from "#imports";
|
|
2
4
|
defineOptions({ name: "EventsUpcomingList" });
|
|
3
5
|
const t = useModuleT("events");
|
|
4
6
|
const format = useFormat();
|
|
5
7
|
const tr = useEventsTranslate();
|
|
6
8
|
const links = useEventLinks();
|
|
7
|
-
const { data, status } = useLazyFetch(
|
|
9
|
+
const { data, status } = useLazyFetch(eventsApi.admin.upcoming, { key: "admin-events-upcoming" });
|
|
8
10
|
</script>
|
|
9
11
|
|
|
10
12
|
<template>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { eventsApi } from "../../utils/api";
|
|
3
|
+
import { useEventLinks, useLazyFetch, useModuleT } from "#imports";
|
|
2
4
|
defineOptions({ name: "EventsUpcomingStat" });
|
|
3
5
|
const t = useModuleT("events");
|
|
4
6
|
const links = useEventLinks();
|
|
5
|
-
const { data, status } = useLazyFetch(
|
|
7
|
+
const { data, status } = useLazyFetch(eventsApi.admin.upcoming, { key: "admin-events-upcoming" });
|
|
6
8
|
</script>
|
|
7
9
|
|
|
8
10
|
<template>
|
|
@@ -131,7 +131,6 @@
|
|
|
131
131
|
"listDescription": "Arrangementer, deres billettyper og deltagere.",
|
|
132
132
|
"body": "Detaljer",
|
|
133
133
|
"bodyHelp": "Vises på arrangementssiden. Markdown understøttes.",
|
|
134
|
-
"save": "Gem",
|
|
135
134
|
"confirmRefund": "Refundér denne billet?",
|
|
136
135
|
"confirmCancel": "Annullér denne billet?",
|
|
137
136
|
"confirmRefundDescription": "Hele beløbet sendes tilbage til {name}, og billetten annulleres.",
|
|
@@ -171,7 +170,6 @@
|
|
|
171
170
|
"requiresHelp": "Gæster uden et passende aktivt medlemskab kan ikke købe denne billet.",
|
|
172
171
|
"excludesMembership": "Ikke for medlemmer af",
|
|
173
172
|
"excludesHelp": "Medlemmer med disse medlemskaber kan ikke købe denne billet, så medlemmer og ikke-medlemmer kan have hver deres pris.",
|
|
174
|
-
"cancel": "Annullér",
|
|
175
173
|
"created": "Arrangementet er oprettet",
|
|
176
174
|
"create": "Nyt arrangement",
|
|
177
175
|
"createDescription": "Det gemmes som kladde, indtil du udgiver det.",
|
|
@@ -225,7 +223,28 @@
|
|
|
225
223
|
"draft": "Kladde",
|
|
226
224
|
"published": "Udgivet",
|
|
227
225
|
"archived": "Arkiveret"
|
|
228
|
-
}
|
|
226
|
+
},
|
|
227
|
+
"nameRequired": "Angiv et navn.",
|
|
228
|
+
"slugRequired": "Angiv et URL-navn.",
|
|
229
|
+
"slugInvalid": "Brug små bogstaver, tal og bindestreger.",
|
|
230
|
+
"startsRequired": "Angiv hvornår arrangementet starter.",
|
|
231
|
+
"keyRequired": "Angiv en nøgle.",
|
|
232
|
+
"priceInvalid": "Angiv en pris på 0 eller mere.",
|
|
233
|
+
"countInvalid": "Angiv et helt tal på 0 eller mere.",
|
|
234
|
+
"groupTitleRequired": "Angiv en titel.",
|
|
235
|
+
"saveType": "Gem billettype",
|
|
236
|
+
"saveGroup": "Gem tilvalgsgruppe",
|
|
237
|
+
"saveAddon": "Gem valg",
|
|
238
|
+
"search": "Søg i arrangementer",
|
|
239
|
+
"noMatches": "Ingen arrangementer matcher din søgning",
|
|
240
|
+
"clearSearch": "Ryd søgning",
|
|
241
|
+
"loadFailed": "Arrangementerne kunne ikke indlæses",
|
|
242
|
+
"forbidden": "Du har ikke adgang til arrangementer",
|
|
243
|
+
"eventLoadFailed": "Arrangementet kunne ikke indlæses",
|
|
244
|
+
"attendeesLoadFailed": "Deltagerne kunne ikke indlæses",
|
|
245
|
+
"allStatuses": "Alle statusser",
|
|
246
|
+
"clearFilters": "Ryd filtre",
|
|
247
|
+
"searchAttendees": "Søg navn eller e-mail"
|
|
229
248
|
},
|
|
230
249
|
"listDescription": "Find et arrangement og få din billet.",
|
|
231
250
|
"noTicketsTitle": "Ingen billetter endnu",
|
|
@@ -131,7 +131,6 @@
|
|
|
131
131
|
"listDescription": "Events, their ticket types and attendees.",
|
|
132
132
|
"body": "Details",
|
|
133
133
|
"bodyHelp": "Shown on the event page. Markdown is supported.",
|
|
134
|
-
"save": "Save",
|
|
135
134
|
"confirmRefund": "Refund this ticket?",
|
|
136
135
|
"confirmCancel": "Cancel this ticket?",
|
|
137
136
|
"confirmRefundDescription": "The full amount goes back to {name} and the ticket is cancelled.",
|
|
@@ -171,7 +170,6 @@
|
|
|
171
170
|
"requiresHelp": "Visitors without a matching active membership cannot buy this ticket.",
|
|
172
171
|
"excludesMembership": "Not for members of",
|
|
173
172
|
"excludesHelp": "Holders of these memberships cannot buy this ticket, so members and non-members can have separate prices.",
|
|
174
|
-
"cancel": "Cancel",
|
|
175
173
|
"created": "Event created",
|
|
176
174
|
"create": "New event",
|
|
177
175
|
"createDescription": "It is saved as a draft until you publish it.",
|
|
@@ -225,7 +223,28 @@
|
|
|
225
223
|
"draft": "Draft",
|
|
226
224
|
"published": "Published",
|
|
227
225
|
"archived": "Archived"
|
|
228
|
-
}
|
|
226
|
+
},
|
|
227
|
+
"nameRequired": "Enter a name.",
|
|
228
|
+
"slugRequired": "Enter a URL name.",
|
|
229
|
+
"slugInvalid": "Use lowercase letters, numbers and dashes.",
|
|
230
|
+
"startsRequired": "Enter when the event starts.",
|
|
231
|
+
"keyRequired": "Enter a key.",
|
|
232
|
+
"priceInvalid": "Enter a price of 0 or more.",
|
|
233
|
+
"countInvalid": "Enter a whole number of 0 or more.",
|
|
234
|
+
"groupTitleRequired": "Enter a title.",
|
|
235
|
+
"saveType": "Save ticket type",
|
|
236
|
+
"saveGroup": "Save add-on group",
|
|
237
|
+
"saveAddon": "Save choice",
|
|
238
|
+
"search": "Search events",
|
|
239
|
+
"noMatches": "No events match your search",
|
|
240
|
+
"clearSearch": "Clear search",
|
|
241
|
+
"loadFailed": "Events could not be loaded",
|
|
242
|
+
"forbidden": "You do not have access to events",
|
|
243
|
+
"eventLoadFailed": "The event could not be loaded",
|
|
244
|
+
"attendeesLoadFailed": "Attendees could not be loaded",
|
|
245
|
+
"allStatuses": "All statuses",
|
|
246
|
+
"clearFilters": "Clear filters",
|
|
247
|
+
"searchAttendees": "Search name or email"
|
|
229
248
|
},
|
|
230
249
|
"listDescription": "Find an event and get your ticket.",
|
|
231
250
|
"noTicketsTitle": "No tickets yet",
|
package/dist/runtime/menus.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
import type { TicketDto } from '../../../types.js';
|
|
3
3
|
/**
|
|
4
|
-
* The caller's tickets: the body of `GET /api/events/tickets
|
|
4
|
+
* The caller's tickets: the body of `GET /api/events/me/tickets` and the account bootstrap's `events:tickets:mine`
|
|
5
5
|
* (the key `useMyTickets()` reads), so both always return the same thing.
|
|
6
6
|
*/
|
|
7
7
|
export default function myTickets(event: H3Event): Promise<TicketDto[]>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export declare const ticketTypeFields: z.ZodObject<{
|
|
3
|
-
currency: z.ZodOptional<z.ZodString>;
|
|
4
3
|
name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
5
4
|
key: z.ZodString;
|
|
5
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
6
|
+
currency: z.ZodOptional<z.ZodString>;
|
|
6
7
|
price: z.ZodOptional<z.ZodNumber>;
|
|
7
8
|
hidePrice: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
-
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
9
9
|
requiresMembership: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>;
|
|
10
10
|
capacity: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
11
11
|
saleStartsAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -101,12 +101,12 @@ export declare const updateEventShape: {
|
|
|
101
101
|
export declare const updateTicketTypeShape: {
|
|
102
102
|
id: z.ZodString;
|
|
103
103
|
patch: z.ZodObject<{
|
|
104
|
-
currency: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
105
104
|
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
106
105
|
key: z.ZodOptional<z.ZodString>;
|
|
106
|
+
description: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>>;
|
|
107
|
+
currency: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
107
108
|
price: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
108
109
|
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
110
|
requiresMembership: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"any">, z.ZodArray<z.ZodString>]>>>>;
|
|
111
111
|
capacity: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
112
112
|
saleStartsAt: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { eq } from "drizzle-orm";
|
|
3
|
+
import { idParams, noContent, notFound } from "@frigear-nu/module-kit/server";
|
|
3
4
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
await
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
await eventsStaff.require(event);
|
|
6
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
7
|
+
const [row] = await db.delete(schema.eventAddonGroups).where(eq(schema.eventAddonGroups.id, id)).returning({ id: schema.eventAddonGroups.id });
|
|
8
|
+
if (!row) throw notFound("Addon group not found.");
|
|
9
|
+
return noContent(event);
|
|
7
10
|
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { eq } from "drizzle-orm";
|
|
3
|
+
import { idParams, notFound } from "@frigear-nu/module-kit/server";
|
|
3
4
|
import { addonGroupInput } from "../../utils/admin-schemas.js";
|
|
4
5
|
const patchSchema = addonGroupInput.omit({ addons: true }).partial();
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
6
|
-
await
|
|
7
|
+
await eventsStaff.require(event);
|
|
8
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
7
9
|
const patch = await readValidatedBody(event, patchSchema.parse);
|
|
8
|
-
const [row] = await db.update(schema.eventAddonGroups).set(patch).where(eq(schema.eventAddonGroups.id,
|
|
9
|
-
if (!row) throw
|
|
10
|
-
return row;
|
|
10
|
+
const [row] = await db.update(schema.eventAddonGroups).set(patch).where(eq(schema.eventAddonGroups.id, id)).returning();
|
|
11
|
+
if (!row) throw notFound("Addon group not found.");
|
|
12
|
+
return toAdminAddonGroupDto(row);
|
|
11
13
|
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { db, schema } from "@nuxthub/db";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { created, idParams, notFound } from "@frigear-nu/module-kit/server";
|
|
1
4
|
import { insertAddonGroup } from "../../utils/admin.js";
|
|
2
5
|
import { addonGroupInput } from "../../utils/admin-schemas.js";
|
|
3
6
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
await
|
|
7
|
+
await eventsStaff.require(event);
|
|
8
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
5
9
|
const input = await readValidatedBody(event, addonGroupInput.parse);
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
setResponseStatus(event, 201);
|
|
10
|
-
return row;
|
|
10
|
+
const [parent] = await db.select({ id: schema.eventTicketTypes.id }).from(schema.eventTicketTypes).where(eq(schema.eventTicketTypes.id, id)).limit(1);
|
|
11
|
+
if (!parent) throw notFound("Ticket type not found.");
|
|
12
|
+
return created(event, toAdminAddonGroupDto(await insertAddonGroup(id, input)));
|
|
11
13
|
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { eq } from "drizzle-orm";
|
|
3
|
+
import { idParams, noContent, notFound } from "@frigear-nu/module-kit/server";
|
|
3
4
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
await
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
await eventsStaff.require(event);
|
|
6
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
7
|
+
const [row] = await db.delete(schema.eventAddons).where(eq(schema.eventAddons.id, id)).returning({ id: schema.eventAddons.id });
|
|
8
|
+
if (!row) throw notFound("Addon not found.");
|
|
9
|
+
return noContent(event);
|
|
7
10
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { eq } from "drizzle-orm";
|
|
3
|
+
import { idParams, notFound } from "@frigear-nu/module-kit/server";
|
|
3
4
|
import { addonInput } from "../../utils/admin-schemas.js";
|
|
5
|
+
const patchSchema = addonInput.partial();
|
|
4
6
|
export default defineEventHandler(async (event) => {
|
|
5
|
-
await
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
await eventsStaff.require(event);
|
|
8
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
9
|
+
const patch = await readValidatedBody(event, patchSchema.parse);
|
|
10
|
+
const [row] = await db.update(schema.eventAddons).set(patch).where(eq(schema.eventAddons.id, id)).returning();
|
|
11
|
+
if (!row) throw notFound("Addon not found.");
|
|
12
|
+
return toAdminAddonDto(row);
|
|
10
13
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { created, idParams, notFound } from "@frigear-nu/module-kit/server";
|
|
2
4
|
import { newId } from "../../../utils/index.js";
|
|
3
5
|
import { addonInput } from "../../utils/admin-schemas.js";
|
|
4
6
|
export default defineEventHandler(async (event) => {
|
|
5
|
-
await
|
|
7
|
+
await eventsStaff.require(event);
|
|
8
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
6
9
|
const input = await readValidatedBody(event, addonInput.parse);
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
return row[0];
|
|
10
|
+
const [parent] = await db.select({ id: schema.eventAddonGroups.id }).from(schema.eventAddonGroups).where(eq(schema.eventAddonGroups.id, id)).limit(1);
|
|
11
|
+
if (!parent) throw notFound("Addon group not found.");
|
|
12
|
+
const [row] = await db.insert(schema.eventAddons).values({ id: newId("adn"), groupId: id, ...input, description: input.description ?? null, capacity: input.capacity ?? null }).returning();
|
|
13
|
+
return created(event, toAdminAddonDto(row));
|
|
12
14
|
});
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
|
-
import {
|
|
2
|
+
import { and, count, eq, or } from "drizzle-orm";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { idParams, likeContains, notFound, orderByQuery, pageQuery, pageRange, paginate } from "@frigear-nu/module-kit/server";
|
|
5
|
+
const querySchema = pageQuery({
|
|
6
|
+
sort: ["createdAt", "status", "checkedInAt", "name", "email"],
|
|
7
|
+
defaultSort: "createdAt",
|
|
8
|
+
defaultOrder: "asc",
|
|
9
|
+
defaultPageSize: 50,
|
|
10
|
+
filters: {
|
|
11
|
+
status: z.enum(["pending", "paid", "cancelled", "refunded", "expired"]).optional()
|
|
12
|
+
}
|
|
13
|
+
});
|
|
3
14
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
await
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
await eventsStaff.require(event);
|
|
16
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
17
|
+
const query = await getValidatedQuery(event, querySchema.parse);
|
|
18
|
+
const [parent] = await db.select({ id: schema.events.id }).from(schema.events).where(eq(schema.events.id, id)).limit(1);
|
|
19
|
+
if (!parent) throw notFound("Event not found.");
|
|
20
|
+
const conditions = [eq(schema.eventTickets.eventId, id)];
|
|
21
|
+
if (query.q) conditions.push(or(likeContains(schema.users.name, query.q), likeContains(schema.users.email, query.q)));
|
|
22
|
+
if (query.status) conditions.push(eq(schema.eventTickets.status, query.status));
|
|
23
|
+
const where = and(...conditions);
|
|
24
|
+
const orderBy = orderByQuery({
|
|
25
|
+
createdAt: schema.eventTickets.createdAt,
|
|
26
|
+
status: schema.eventTickets.status,
|
|
27
|
+
checkedInAt: schema.eventTickets.checkedInAt,
|
|
28
|
+
name: schema.users.name,
|
|
29
|
+
email: schema.users.email
|
|
30
|
+
}, query, { tiebreaker: schema.eventTickets.id });
|
|
31
|
+
const { limit, offset } = pageRange(query);
|
|
32
|
+
const [rows, [totals]] = await Promise.all([
|
|
33
|
+
db.select({ ticket: schema.eventTickets, userName: schema.users.name, userEmail: schema.users.email }).from(schema.eventTickets).leftJoin(schema.users, eq(schema.eventTickets.userId, schema.users.id)).where(where).orderBy(...orderBy).limit(limit).offset(offset),
|
|
34
|
+
db.select({ total: count() }).from(schema.eventTickets).leftJoin(schema.users, eq(schema.eventTickets.userId, schema.users.id)).where(where)
|
|
35
|
+
]);
|
|
36
|
+
return paginate({
|
|
37
|
+
total: totals?.total ?? 0,
|
|
38
|
+
query,
|
|
39
|
+
items: rows.map(({ ticket, userName, userEmail }) => toAttendeeDto(ticket, { name: userName ?? null, email: userEmail ?? null }))
|
|
40
|
+
});
|
|
11
41
|
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { and, eq, isNull } from "drizzle-orm";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { conflict, notFound } from "@frigear-nu/module-kit/server";
|
|
4
5
|
const bodySchema = z.object({ code: z.string().min(1) });
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
6
|
-
const { userId } = await
|
|
7
|
+
const { userId } = await eventsStaff.require(event);
|
|
7
8
|
const { code } = await readValidatedBody(event, bodySchema.parse);
|
|
8
9
|
const [ticket] = await db.select().from(schema.eventTickets).where(eq(schema.eventTickets.code, code)).limit(1);
|
|
9
|
-
if (!ticket) throw
|
|
10
|
-
if (ticket.status !== "paid") throw
|
|
10
|
+
if (!ticket) throw notFound("Unknown ticket code.");
|
|
11
|
+
if (ticket.status !== "paid") throw conflict(`This ticket is ${ticket.status}, not valid for entry.`, { status: ticket.status });
|
|
11
12
|
const [claimed] = await db.update(schema.eventTickets).set({ checkedInAt: /* @__PURE__ */ new Date(), checkedInBy: userId }).where(and(eq(schema.eventTickets.id, ticket.id), isNull(schema.eventTickets.checkedInAt))).returning();
|
|
12
13
|
const row = claimed ?? (await db.select().from(schema.eventTickets).where(eq(schema.eventTickets.id, ticket.id)).limit(1))[0];
|
|
13
14
|
const [dto] = await toTicketDtos([row]);
|
|
@@ -1,31 +1,49 @@
|
|
|
1
1
|
import { asc, eq, inArray } from "drizzle-orm";
|
|
2
2
|
import { db, schema } from "@nuxthub/db";
|
|
3
|
+
import { created, idParams, notFound, uniqueCopySlug } from "@frigear-nu/module-kit/server";
|
|
3
4
|
import { newId } from "../../../utils/index.js";
|
|
4
5
|
export default defineEventHandler(async (event) => {
|
|
5
|
-
await
|
|
6
|
-
const id =
|
|
6
|
+
await eventsStaff.require(event);
|
|
7
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
7
8
|
const [source] = await db.select().from(schema.events).where(eq(schema.events.id, id)).limit(1);
|
|
8
|
-
if (!source) throw
|
|
9
|
-
const
|
|
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();
|
|
9
|
+
if (!source) throw notFound("Event not found.");
|
|
10
|
+
const slug = await uniqueCopySlug(source.slug, async (candidate) => (await db.select({ id: schema.events.id }).from(schema.events).where(eq(schema.events.slug, candidate)).limit(1)).length > 0);
|
|
14
11
|
const types = await db.select().from(schema.eventTicketTypes).where(eq(schema.eventTicketTypes.eventId, id)).orderBy(asc(schema.eventTicketTypes.sortOrder));
|
|
15
12
|
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
13
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const { id: _id, createdAt: _c, updatedAt: _u, ...fields } = source;
|
|
15
|
+
const copyId = newId("evn");
|
|
16
|
+
const inserted = { types: [], groups: [], addons: [] };
|
|
17
|
+
try {
|
|
18
|
+
const [copy] = await db.insert(schema.events).values({ ...fields, id: copyId, slug, status: "draft" }).returning();
|
|
19
|
+
for (const type of types) {
|
|
20
|
+
const { id: _t, createdAt: _tc, updatedAt: _tu, ...typeFields } = type;
|
|
21
|
+
const typeId = newId("ttp");
|
|
22
|
+
await db.insert(schema.eventTicketTypes).values({ ...typeFields, id: typeId, eventId: copyId });
|
|
23
|
+
inserted.types.push(typeId);
|
|
24
|
+
for (const group of groups.filter((g) => g.ticketTypeId === type.id)) {
|
|
25
|
+
const { id: _g, ...groupFields } = group;
|
|
26
|
+
const groupId = newId("agr");
|
|
27
|
+
await db.insert(schema.eventAddonGroups).values({ ...groupFields, id: groupId, ticketTypeId: typeId });
|
|
28
|
+
inserted.groups.push(groupId);
|
|
29
|
+
for (const addon of addons.filter((a) => a.groupId === group.id)) {
|
|
30
|
+
const { id: _a, ...addonFields } = addon;
|
|
31
|
+
const addonId = newId("adn");
|
|
32
|
+
await db.insert(schema.eventAddons).values({ ...addonFields, id: addonId, groupId });
|
|
33
|
+
inserted.addons.push(addonId);
|
|
34
|
+
}
|
|
26
35
|
}
|
|
27
36
|
}
|
|
37
|
+
return created(event, toAdminEventDto(copy));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (inserted.addons.length) await db.delete(schema.eventAddons).where(inArray(schema.eventAddons.id, inserted.addons)).catch(() => {
|
|
40
|
+
});
|
|
41
|
+
if (inserted.groups.length) await db.delete(schema.eventAddonGroups).where(inArray(schema.eventAddonGroups.id, inserted.groups)).catch(() => {
|
|
42
|
+
});
|
|
43
|
+
if (inserted.types.length) await db.delete(schema.eventTicketTypes).where(inArray(schema.eventTicketTypes.id, inserted.types)).catch(() => {
|
|
44
|
+
});
|
|
45
|
+
await db.delete(schema.events).where(eq(schema.events.id, copyId)).catch(() => {
|
|
46
|
+
});
|
|
47
|
+
throw error;
|
|
28
48
|
}
|
|
29
|
-
setResponseStatus(event, 201);
|
|
30
|
-
return copy;
|
|
31
49
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { and, eq, sql } from "drizzle-orm";
|
|
3
|
+
import { conflict, idParams, noContent, notFound } from "@frigear-nu/module-kit/server";
|
|
3
4
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
await
|
|
5
|
-
const id =
|
|
5
|
+
await eventsStaff.require(event);
|
|
6
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
7
|
+
const [existing] = await db.select({ id: schema.events.id }).from(schema.events).where(eq(schema.events.id, id)).limit(1);
|
|
8
|
+
if (!existing) throw notFound("Event not found.");
|
|
6
9
|
const [sold] = await db.select({ count: sql`count(*)` }).from(schema.eventTickets).where(and(eq(schema.eventTickets.eventId, id), eq(schema.eventTickets.status, "paid")));
|
|
7
|
-
if (Number(sold?.count ?? 0) > 0) throw
|
|
10
|
+
if (Number(sold?.count ?? 0) > 0) throw conflict("This event has paid tickets. Archive it instead.");
|
|
8
11
|
await db.delete(schema.events).where(eq(schema.events.id, id));
|
|
9
|
-
return
|
|
12
|
+
return noContent(event);
|
|
10
13
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { idParams } from "@frigear-nu/module-kit/server";
|
|
1
2
|
import { getEventWithTicketTypes } from "../../utils/admin.js";
|
|
2
3
|
export default defineEventHandler(async (event) => {
|
|
3
|
-
await
|
|
4
|
-
|
|
4
|
+
await eventsStaff.require(event);
|
|
5
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
6
|
+
return toAdminEventDetailDto(await getEventWithTicketTypes(id));
|
|
5
7
|
});
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { idParams } from "@frigear-nu/module-kit/server";
|
|
1
2
|
import { eventPatchSchema, patchEventRow } from "../../utils/admin.js";
|
|
2
3
|
export default defineEventHandler(async (event) => {
|
|
3
|
-
await
|
|
4
|
+
await eventsStaff.require(event);
|
|
5
|
+
const { id } = await getValidatedRouterParams(event, idParams().parse);
|
|
4
6
|
const patch = await readValidatedBody(event, eventPatchSchema.parse);
|
|
5
|
-
return patchEventRow(
|
|
7
|
+
return toAdminEventDto(await patchEventRow(id, patch));
|
|
6
8
|
});
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
|
-
import {
|
|
2
|
+
import { and, count, eq, is, or, sql } from "drizzle-orm";
|
|
3
|
+
import { PgTable } from "drizzle-orm/pg-core";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { escapeLike, likeContains, orderByQuery, pageQuery, pageRange, paginate } from "@frigear-nu/module-kit/server";
|
|
6
|
+
function translatedContains(column, value) {
|
|
7
|
+
const pattern = `%${escapeLike(value)}%`;
|
|
8
|
+
if (is(schema.events, PgTable)) {
|
|
9
|
+
return sql`exists (select 1 from jsonb_each_text(case when jsonb_typeof(${column}) = 'object' then ${column} else jsonb_build_object('_', ${column}) end) as t where lower(t.value) like lower(${pattern}) escape '\\')`;
|
|
10
|
+
}
|
|
11
|
+
return sql`exists (select 1 from json_each(${column}) as t where lower(t.value) like lower(${pattern}) escape '\\')`;
|
|
12
|
+
}
|
|
13
|
+
const querySchema = pageQuery({
|
|
14
|
+
sort: ["startsAt", "createdAt", "slug", "status"],
|
|
15
|
+
defaultSort: "startsAt",
|
|
16
|
+
filters: {
|
|
17
|
+
status: z.enum(["draft", "published", "archived"]).optional()
|
|
18
|
+
}
|
|
19
|
+
});
|
|
3
20
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
await
|
|
5
|
-
|
|
21
|
+
await eventsStaff.require(event);
|
|
22
|
+
const query = await getValidatedQuery(event, querySchema.parse);
|
|
23
|
+
const conditions = [];
|
|
24
|
+
if (query.q) conditions.push(or(translatedContains(schema.events.name, query.q), likeContains(schema.events.slug, query.q)));
|
|
25
|
+
if (query.status) conditions.push(eq(schema.events.status, query.status));
|
|
26
|
+
const where = conditions.length ? and(...conditions) : void 0;
|
|
27
|
+
const orderBy = orderByQuery({
|
|
28
|
+
startsAt: schema.events.startsAt,
|
|
29
|
+
createdAt: schema.events.createdAt,
|
|
30
|
+
slug: schema.events.slug,
|
|
31
|
+
status: schema.events.status
|
|
32
|
+
}, query, { tiebreaker: schema.events.id });
|
|
33
|
+
const { limit, offset } = pageRange(query);
|
|
34
|
+
const [rows, [totals]] = await Promise.all([
|
|
35
|
+
db.select().from(schema.events).where(where).orderBy(...orderBy).limit(limit).offset(offset),
|
|
36
|
+
db.select({ total: count() }).from(schema.events).where(where)
|
|
37
|
+
]);
|
|
38
|
+
return paginate({ total: totals?.total ?? 0, query, items: rows.map(toAdminEventDto) });
|
|
6
39
|
});
|