@lcas58/esmi-api-types 1.0.27 → 1.0.29

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.
@@ -0,0 +1,204 @@
1
+ import { and, count, eq, gte, ilike, lte, or, sql } from "drizzle-orm";
2
+ import * as HttpStatusCodes from "stoker/http-status-codes";
3
+ import * as HttpStatusPhrases from "stoker/http-status-phrases";
4
+ import { createDb } from "../../db/index.js";
5
+ import { event, group } from "../../db/schema/index.js";
6
+ function slugify(name) {
7
+ return name
8
+ .toLowerCase()
9
+ .replace(/[^\w\s-]/g, "")
10
+ .replace(/[\s_]+/g, "-")
11
+ .replace(/^-+|-+$/g, "")
12
+ .slice(0, 255);
13
+ }
14
+ export const list = async (c) => {
15
+ const { db } = createDb(c.env);
16
+ const query = c.req.valid("query");
17
+ const { sportId, platform, search } = query;
18
+ const limit = query.limit ?? 20;
19
+ const offset = query.offset ?? 0;
20
+ const city = query.city?.trim();
21
+ const state = query.state?.trim();
22
+ const whereConditions = [eq(group.isActive, true)];
23
+ if (sportId) {
24
+ whereConditions.push(eq(group.sportId, sportId));
25
+ }
26
+ if (platform) {
27
+ whereConditions.push(eq(group.platform, platform));
28
+ }
29
+ if (city) {
30
+ whereConditions.push(ilike(group.city, city));
31
+ }
32
+ if (state) {
33
+ whereConditions.push(ilike(group.state, state));
34
+ }
35
+ if (search) {
36
+ whereConditions.push(or(ilike(group.name, `%${search}%`), ilike(group.description, `%${search}%`)));
37
+ }
38
+ const groups = await db.query.group.findMany({
39
+ where: and(...whereConditions),
40
+ with: {
41
+ sport: true,
42
+ creator: {
43
+ columns: {
44
+ id: true,
45
+ name: true,
46
+ image: true,
47
+ },
48
+ },
49
+ },
50
+ limit,
51
+ offset,
52
+ orderBy: (group, { asc }) => [asc(group.name)],
53
+ });
54
+ // Count events per group
55
+ const groupIds = groups.map(g => g.id);
56
+ let eventCounts = {};
57
+ if (groupIds.length > 0) {
58
+ const counts = await db
59
+ .select({
60
+ groupId: event.groupId,
61
+ count: count(),
62
+ })
63
+ .from(event)
64
+ .where(and(sql `${event.groupId} IN ${groupIds}`, gte(event.startsAt, new Date())))
65
+ .groupBy(event.groupId);
66
+ eventCounts = Object.fromEntries(counts.map(r => [r.groupId, r.count]));
67
+ }
68
+ const result = groups.map(g => ({
69
+ ...g,
70
+ _count: { events: eventCounts[g.id] ?? 0 },
71
+ }));
72
+ return c.json(result, HttpStatusCodes.OK);
73
+ };
74
+ export const getOne = async (c) => {
75
+ const { db } = createDb(c.env);
76
+ const { id } = c.req.valid("param");
77
+ const foundGroup = await db.query.group.findFirst({
78
+ where: eq(group.id, id),
79
+ with: {
80
+ sport: true,
81
+ creator: {
82
+ columns: {
83
+ id: true,
84
+ name: true,
85
+ image: true,
86
+ },
87
+ },
88
+ },
89
+ });
90
+ if (!foundGroup) {
91
+ return c.json({ message: HttpStatusPhrases.NOT_FOUND }, HttpStatusCodes.NOT_FOUND);
92
+ }
93
+ return c.json(foundGroup, HttpStatusCodes.OK);
94
+ };
95
+ export const getEvents = async (c) => {
96
+ const { db } = createDb(c.env);
97
+ const { id } = c.req.valid("param");
98
+ const query = c.req.valid("query");
99
+ const foundGroup = await db.query.group.findFirst({
100
+ where: eq(group.id, id),
101
+ columns: { id: true },
102
+ });
103
+ if (!foundGroup) {
104
+ return c.json({ message: HttpStatusPhrases.NOT_FOUND }, HttpStatusCodes.NOT_FOUND);
105
+ }
106
+ const limit = query.limit ?? 20;
107
+ const offset = query.offset ?? 0;
108
+ const whereConditions = [eq(event.groupId, id)];
109
+ if (query.from) {
110
+ whereConditions.push(gte(event.startsAt, query.from));
111
+ }
112
+ if (query.to) {
113
+ const to = new Date(query.to);
114
+ if (to.getUTCHours() === 0 && to.getUTCMinutes() === 0 && to.getUTCSeconds() === 0) {
115
+ to.setUTCHours(23, 59, 59, 999);
116
+ }
117
+ whereConditions.push(lte(event.startsAt, to));
118
+ }
119
+ const events = await db.query.event.findMany({
120
+ where: and(...whereConditions),
121
+ with: {
122
+ sport: true,
123
+ location: true,
124
+ externalLink: true,
125
+ creator: {
126
+ columns: {
127
+ id: true,
128
+ name: true,
129
+ image: true,
130
+ },
131
+ },
132
+ },
133
+ limit,
134
+ offset,
135
+ orderBy: (event, { asc }) => [asc(event.startsAt)],
136
+ });
137
+ return c.json(events, HttpStatusCodes.OK);
138
+ };
139
+ export const create = async (c) => {
140
+ const { db } = createDb(c.env);
141
+ const body = c.req.valid("json");
142
+ const user = c.get("user");
143
+ const slug = body.slug || slugify(body.name);
144
+ const [newGroup] = await db.insert(group).values({
145
+ name: body.name,
146
+ slug,
147
+ description: body.description,
148
+ sportId: body.sportId,
149
+ city: body.city,
150
+ state: body.state,
151
+ imageUrl: body.imageUrl,
152
+ createdByUserId: user.id,
153
+ }).returning();
154
+ const result = await db.query.group.findFirst({
155
+ where: eq(group.id, newGroup.id),
156
+ with: {
157
+ sport: true,
158
+ creator: {
159
+ columns: {
160
+ id: true,
161
+ name: true,
162
+ image: true,
163
+ },
164
+ },
165
+ },
166
+ });
167
+ return c.json(result, HttpStatusCodes.OK);
168
+ };
169
+ export const update = async (c) => {
170
+ const { db } = createDb(c.env);
171
+ const { id } = c.req.valid("param");
172
+ const body = c.req.valid("json");
173
+ const user = c.get("user");
174
+ const existing = await db.query.group.findFirst({
175
+ where: eq(group.id, id),
176
+ });
177
+ if (!existing) {
178
+ return c.json({ message: HttpStatusPhrases.NOT_FOUND }, HttpStatusCodes.NOT_FOUND);
179
+ }
180
+ if (existing.platform) {
181
+ return c.json({ message: "Cannot update external groups" }, HttpStatusCodes.UNAUTHORIZED);
182
+ }
183
+ if (existing.createdByUserId !== user.id) {
184
+ return c.json({ message: HttpStatusPhrases.UNAUTHORIZED }, HttpStatusCodes.UNAUTHORIZED);
185
+ }
186
+ await db.update(group).set({
187
+ ...body,
188
+ updatedAt: new Date(),
189
+ }).where(eq(group.id, id));
190
+ const result = await db.query.group.findFirst({
191
+ where: eq(group.id, id),
192
+ with: {
193
+ sport: true,
194
+ creator: {
195
+ columns: {
196
+ id: true,
197
+ name: true,
198
+ image: true,
199
+ },
200
+ },
201
+ },
202
+ });
203
+ return c.json(result, HttpStatusCodes.OK);
204
+ };
@@ -0,0 +1,334 @@
1
+ declare const router: import("@hono/zod-openapi").OpenAPIHono<import("../../shared/index.js").AppBindings, {
2
+ "/groups": {
3
+ $get: {
4
+ input: {
5
+ query: {
6
+ search?: string | string[] | undefined;
7
+ sportId?: string | string[] | undefined;
8
+ city?: string | string[] | undefined;
9
+ state?: string | string[] | undefined;
10
+ platform?: string | string[] | undefined;
11
+ limit?: string | string[] | undefined;
12
+ offset?: string | string[] | undefined;
13
+ };
14
+ };
15
+ output: {
16
+ id: string;
17
+ description: string | null;
18
+ name: string;
19
+ createdAt: string;
20
+ sport: {
21
+ id: string;
22
+ name: string;
23
+ icon: string | null;
24
+ } | null;
25
+ sportId: string;
26
+ city: string | null;
27
+ state: string | null;
28
+ country: string;
29
+ slug: string;
30
+ platform: string | null;
31
+ externalUrl: string | null;
32
+ updatedAt: string;
33
+ createdByUserId: string | null;
34
+ imageUrl: string | null;
35
+ isActive: boolean;
36
+ creator: {
37
+ image: string | null;
38
+ id: string;
39
+ name: string;
40
+ } | null;
41
+ _count?: {
42
+ events: number;
43
+ } | undefined;
44
+ }[];
45
+ outputFormat: "text" | "json";
46
+ status: 200;
47
+ };
48
+ };
49
+ } & {
50
+ "/groups/:id": {
51
+ $get: {
52
+ input: {
53
+ param: {
54
+ id: string;
55
+ };
56
+ };
57
+ output: {
58
+ message: string;
59
+ };
60
+ outputFormat: "text" | "json";
61
+ status: 404;
62
+ } | {
63
+ input: {
64
+ param: {
65
+ id: string;
66
+ };
67
+ };
68
+ output: {
69
+ id: string;
70
+ description: string | null;
71
+ name: string;
72
+ createdAt: string;
73
+ sport: {
74
+ id: string;
75
+ name: string;
76
+ icon: string | null;
77
+ } | null;
78
+ sportId: string;
79
+ city: string | null;
80
+ state: string | null;
81
+ country: string;
82
+ slug: string;
83
+ platform: string | null;
84
+ externalUrl: string | null;
85
+ updatedAt: string;
86
+ createdByUserId: string | null;
87
+ imageUrl: string | null;
88
+ isActive: boolean;
89
+ creator: {
90
+ image: string | null;
91
+ id: string;
92
+ name: string;
93
+ } | null;
94
+ _count?: {
95
+ events: number;
96
+ } | undefined;
97
+ };
98
+ outputFormat: "text" | "json";
99
+ status: 200;
100
+ };
101
+ };
102
+ } & {
103
+ "/groups/:id/events": {
104
+ $get: {
105
+ input: {
106
+ param: {
107
+ id: string;
108
+ };
109
+ } & {
110
+ query: {
111
+ from?: string | string[] | undefined;
112
+ to?: string | string[] | undefined;
113
+ limit?: string | string[] | undefined;
114
+ offset?: string | string[] | undefined;
115
+ };
116
+ };
117
+ output: {
118
+ metadata: any;
119
+ id: string;
120
+ description: string | null;
121
+ title: string;
122
+ createdAt: string;
123
+ sport: {
124
+ id: string;
125
+ name: string;
126
+ icon: string | null;
127
+ } | null;
128
+ sportId: string;
129
+ startsAt: string;
130
+ endsAt: string | null;
131
+ timezone: string;
132
+ location: {
133
+ id: string;
134
+ name: string;
135
+ formattedAddress: string;
136
+ city: string | null;
137
+ state: string | null;
138
+ latitude: number | null;
139
+ longitude: number | null;
140
+ } | null;
141
+ locationId: string | null;
142
+ externalLinkId: string | null;
143
+ source: "external" | "community";
144
+ thumbnailUrl: string | null;
145
+ createdByUserId: string | null;
146
+ groupId: string | null;
147
+ creator: {
148
+ image: string | null;
149
+ id: string;
150
+ name: string;
151
+ } | null;
152
+ externalLink: {
153
+ url: string;
154
+ id: string;
155
+ domain: string;
156
+ } | null;
157
+ }[];
158
+ outputFormat: "text" | "json";
159
+ status: 200;
160
+ } | {
161
+ input: {
162
+ param: {
163
+ id: string;
164
+ };
165
+ } & {
166
+ query: {
167
+ from?: string | string[] | undefined;
168
+ to?: string | string[] | undefined;
169
+ limit?: string | string[] | undefined;
170
+ offset?: string | string[] | undefined;
171
+ };
172
+ };
173
+ output: {
174
+ message: string;
175
+ };
176
+ outputFormat: "text" | "json";
177
+ status: 404;
178
+ };
179
+ };
180
+ } & {
181
+ "/groups": {
182
+ $post: {
183
+ input: {
184
+ json: {
185
+ name: string;
186
+ sportId: string;
187
+ description?: string | undefined;
188
+ city?: string | undefined;
189
+ state?: string | undefined;
190
+ slug?: string | undefined;
191
+ imageUrl?: string | undefined;
192
+ };
193
+ };
194
+ output: {
195
+ message: string;
196
+ };
197
+ outputFormat: "text" | "json";
198
+ status: 401;
199
+ } | {
200
+ input: {
201
+ json: {
202
+ name: string;
203
+ sportId: string;
204
+ description?: string | undefined;
205
+ city?: string | undefined;
206
+ state?: string | undefined;
207
+ slug?: string | undefined;
208
+ imageUrl?: string | undefined;
209
+ };
210
+ };
211
+ output: {
212
+ id: string;
213
+ description: string | null;
214
+ name: string;
215
+ createdAt: string;
216
+ sport: {
217
+ id: string;
218
+ name: string;
219
+ icon: string | null;
220
+ } | null;
221
+ sportId: string;
222
+ city: string | null;
223
+ state: string | null;
224
+ country: string;
225
+ slug: string;
226
+ platform: string | null;
227
+ externalUrl: string | null;
228
+ updatedAt: string;
229
+ createdByUserId: string | null;
230
+ imageUrl: string | null;
231
+ isActive: boolean;
232
+ creator: {
233
+ image: string | null;
234
+ id: string;
235
+ name: string;
236
+ } | null;
237
+ _count?: {
238
+ events: number;
239
+ } | undefined;
240
+ };
241
+ outputFormat: "text" | "json";
242
+ status: 200;
243
+ };
244
+ };
245
+ } & {
246
+ "/groups/:id": {
247
+ $patch: {
248
+ input: {
249
+ param: {
250
+ id: string;
251
+ };
252
+ } & {
253
+ json: {
254
+ description?: string | undefined;
255
+ name?: string | undefined;
256
+ city?: string | undefined;
257
+ state?: string | undefined;
258
+ imageUrl?: string | null | undefined;
259
+ };
260
+ };
261
+ output: {
262
+ message: string;
263
+ };
264
+ outputFormat: "text" | "json";
265
+ status: 404;
266
+ } | {
267
+ input: {
268
+ param: {
269
+ id: string;
270
+ };
271
+ } & {
272
+ json: {
273
+ description?: string | undefined;
274
+ name?: string | undefined;
275
+ city?: string | undefined;
276
+ state?: string | undefined;
277
+ imageUrl?: string | null | undefined;
278
+ };
279
+ };
280
+ output: {
281
+ message: string;
282
+ };
283
+ outputFormat: "text" | "json";
284
+ status: 401;
285
+ } | {
286
+ input: {
287
+ param: {
288
+ id: string;
289
+ };
290
+ } & {
291
+ json: {
292
+ description?: string | undefined;
293
+ name?: string | undefined;
294
+ city?: string | undefined;
295
+ state?: string | undefined;
296
+ imageUrl?: string | null | undefined;
297
+ };
298
+ };
299
+ output: {
300
+ id: string;
301
+ description: string | null;
302
+ name: string;
303
+ createdAt: string;
304
+ sport: {
305
+ id: string;
306
+ name: string;
307
+ icon: string | null;
308
+ } | null;
309
+ sportId: string;
310
+ city: string | null;
311
+ state: string | null;
312
+ country: string;
313
+ slug: string;
314
+ platform: string | null;
315
+ externalUrl: string | null;
316
+ updatedAt: string;
317
+ createdByUserId: string | null;
318
+ imageUrl: string | null;
319
+ isActive: boolean;
320
+ creator: {
321
+ image: string | null;
322
+ id: string;
323
+ name: string;
324
+ } | null;
325
+ _count?: {
326
+ events: number;
327
+ } | undefined;
328
+ };
329
+ outputFormat: "text" | "json";
330
+ status: 200;
331
+ };
332
+ };
333
+ }, "/">;
334
+ export default router;
@@ -0,0 +1,10 @@
1
+ import { createRouter } from "../../lib/create-app.js";
2
+ import * as handlers from "./groups.handlers.js";
3
+ import * as routes from "./groups.routes.js";
4
+ const router = createRouter()
5
+ .openapi(routes.list, handlers.list)
6
+ .openapi(routes.getOne, handlers.getOne)
7
+ .openapi(routes.getEvents, handlers.getEvents)
8
+ .openapi(routes.create, handlers.create)
9
+ .openapi(routes.update, handlers.update);
10
+ export default router;