@structcms/api 0.1.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.
@@ -0,0 +1,1114 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/next/index.ts
31
+ var next_exports = {};
32
+ __export(next_exports, {
33
+ createAuthenticatedRoute: () => createAuthenticatedRoute,
34
+ createNextAuthCurrentUserRoute: () => createNextAuthCurrentUserRoute,
35
+ createNextAuthOAuthRoute: () => createNextAuthOAuthRoute,
36
+ createNextAuthRefreshRoute: () => createNextAuthRefreshRoute,
37
+ createNextAuthSignInRoute: () => createNextAuthSignInRoute,
38
+ createNextAuthSignOutRoute: () => createNextAuthSignOutRoute,
39
+ createNextAuthVerifyRoute: () => createNextAuthVerifyRoute,
40
+ createNextMediaByIdRoute: () => createNextMediaByIdRoute,
41
+ createNextMediaRoute: () => createNextMediaRoute,
42
+ createNextNavigationByIdRoute: () => createNextNavigationByIdRoute,
43
+ createNextNavigationRoute: () => createNextNavigationRoute,
44
+ createNextPageByIdRoute: () => createNextPageByIdRoute,
45
+ createNextPageBySlugRoute: () => createNextPageBySlugRoute,
46
+ createNextPagesRoute: () => createNextPagesRoute
47
+ });
48
+ module.exports = __toCommonJS(next_exports);
49
+
50
+ // src/media/resolve.ts
51
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
52
+ var MEDIA_FIELD_SUFFIXES = ["_image", "_media", "_photo", "_thumbnail", "_avatar", "_icon"];
53
+ function isMediaField(fieldName) {
54
+ const lower = fieldName.toLowerCase();
55
+ if (lower === "image" || lower === "media" || lower === "photo" || lower === "thumbnail" || lower === "avatar" || lower === "icon") {
56
+ return true;
57
+ }
58
+ return MEDIA_FIELD_SUFFIXES.some((suffix) => lower.endsWith(suffix));
59
+ }
60
+ function isMediaId(value) {
61
+ return typeof value === "string" && UUID_PATTERN.test(value);
62
+ }
63
+ async function resolveDataObject(data, adapter) {
64
+ const resolved = {};
65
+ for (const [key, value] of Object.entries(data)) {
66
+ if (isMediaField(key) && isMediaId(value)) {
67
+ const media = await adapter.getMedia(value);
68
+ resolved[key] = media ? media.url : null;
69
+ } else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
70
+ resolved[key] = await resolveDataObject(value, adapter);
71
+ } else {
72
+ resolved[key] = value;
73
+ }
74
+ }
75
+ return resolved;
76
+ }
77
+ async function resolveMediaReferences(sections, adapter) {
78
+ const resolved = [];
79
+ for (const section of sections) {
80
+ const resolvedData = await resolveDataObject(section.data, adapter);
81
+ resolved.push({
82
+ id: section.id,
83
+ type: section.type,
84
+ data: resolvedData
85
+ });
86
+ }
87
+ return resolved;
88
+ }
89
+
90
+ // src/delivery/handlers.ts
91
+ async function toPageResponse(page, mediaAdapter) {
92
+ const resolvedSections = await resolveMediaReferences(page.sections, mediaAdapter);
93
+ return {
94
+ id: page.id,
95
+ slug: page.slug,
96
+ pageType: page.pageType,
97
+ title: page.title,
98
+ sections: resolvedSections,
99
+ meta: {
100
+ createdAt: page.createdAt.toISOString(),
101
+ updatedAt: page.updatedAt.toISOString()
102
+ }
103
+ };
104
+ }
105
+ async function handleListPages(adapter, mediaAdapter, options) {
106
+ const pages = await adapter.listPages({
107
+ pageType: options?.pageType,
108
+ limit: options?.limit,
109
+ offset: options?.offset
110
+ });
111
+ const results = [];
112
+ for (const page of pages) {
113
+ results.push(await toPageResponse(page, mediaAdapter));
114
+ }
115
+ return results;
116
+ }
117
+ async function handleGetPageBySlug(adapter, mediaAdapter, slug) {
118
+ const page = await adapter.getPage(slug);
119
+ if (!page) {
120
+ return null;
121
+ }
122
+ return toPageResponse(page, mediaAdapter);
123
+ }
124
+
125
+ // src/media/types.ts
126
+ var ALLOWED_MIME_TYPES = [
127
+ "image/jpeg",
128
+ "image/png",
129
+ "image/gif",
130
+ "image/webp",
131
+ "image/svg+xml"
132
+ ];
133
+
134
+ // src/media/handlers.ts
135
+ var MediaValidationError = class extends Error {
136
+ constructor(message, code) {
137
+ super(message);
138
+ this.code = code;
139
+ this.name = "MediaValidationError";
140
+ }
141
+ };
142
+ function validateMimeType(mimeType) {
143
+ const allowed = ALLOWED_MIME_TYPES;
144
+ if (!allowed.includes(mimeType)) {
145
+ throw new MediaValidationError(
146
+ `Invalid file type: ${mimeType}. Allowed types: ${ALLOWED_MIME_TYPES.join(", ")}`,
147
+ "INVALID_MIME_TYPE"
148
+ );
149
+ }
150
+ }
151
+ async function handleUploadMedia(adapter, input) {
152
+ validateMimeType(input.mimeType);
153
+ return adapter.upload(input);
154
+ }
155
+ async function handleGetMedia(adapter, id) {
156
+ return adapter.getMedia(id);
157
+ }
158
+ async function handleListMedia(adapter, filter) {
159
+ return adapter.listMedia(filter);
160
+ }
161
+ async function handleDeleteMedia(adapter, id) {
162
+ return adapter.deleteMedia(id);
163
+ }
164
+
165
+ // src/utils/sanitize.ts
166
+ var import_sanitize_html = __toESM(require("sanitize-html"), 1);
167
+ var ALLOWED_TAGS = [
168
+ "p",
169
+ "h1",
170
+ "h2",
171
+ "h3",
172
+ "h4",
173
+ "h5",
174
+ "h6",
175
+ "ul",
176
+ "ol",
177
+ "li",
178
+ "a",
179
+ "strong",
180
+ "em",
181
+ "br",
182
+ "blockquote",
183
+ "code",
184
+ "pre",
185
+ "img"
186
+ ];
187
+ var ALLOWED_ATTRIBUTES = {
188
+ a: ["href"],
189
+ img: ["src", "alt"]
190
+ };
191
+ var SANITIZE_OPTIONS = {
192
+ allowedTags: ALLOWED_TAGS,
193
+ allowedAttributes: ALLOWED_ATTRIBUTES,
194
+ disallowedTagsMode: "discard"
195
+ };
196
+ function sanitizeString(value) {
197
+ return (0, import_sanitize_html.default)(value, SANITIZE_OPTIONS);
198
+ }
199
+ function sanitizeValue(value) {
200
+ if (typeof value === "string") {
201
+ return sanitizeString(value);
202
+ }
203
+ if (Array.isArray(value)) {
204
+ return value.map(sanitizeValue);
205
+ }
206
+ if (value !== null && typeof value === "object") {
207
+ const result = {};
208
+ for (const [key, val] of Object.entries(value)) {
209
+ result[key] = sanitizeValue(val);
210
+ }
211
+ return result;
212
+ }
213
+ return value;
214
+ }
215
+ function sanitizeSectionData(sections) {
216
+ return sections.map((section) => ({
217
+ ...section,
218
+ data: sanitizeValue(section.data)
219
+ }));
220
+ }
221
+
222
+ // src/utils/slug.ts
223
+ var UMLAUT_MAP = {
224
+ \u00E4: "ae",
225
+ \u00F6: "oe",
226
+ \u00FC: "ue",
227
+ \u00C4: "Ae",
228
+ \u00D6: "Oe",
229
+ \u00DC: "Ue",
230
+ \u00DF: "ss"
231
+ };
232
+ function generateSlug(title) {
233
+ let slug = title.toLowerCase().trim();
234
+ for (const [umlaut, replacement] of Object.entries(UMLAUT_MAP)) {
235
+ slug = slug.replace(new RegExp(umlaut, "g"), replacement.toLowerCase());
236
+ }
237
+ slug = slug.replace(/[\s_]+/g, "-");
238
+ slug = slug.replace(/[^a-z0-9-]/g, "");
239
+ slug = slug.replace(/-+/g, "-");
240
+ slug = slug.replace(/^-+|-+$/g, "");
241
+ return slug;
242
+ }
243
+ function ensureUniqueSlug(slug, existingSlugs) {
244
+ if (!existingSlugs.includes(slug)) {
245
+ return slug;
246
+ }
247
+ let counter = 1;
248
+ let uniqueSlug = `${slug}-${counter}`;
249
+ while (existingSlugs.includes(uniqueSlug)) {
250
+ counter++;
251
+ uniqueSlug = `${slug}-${counter}`;
252
+ }
253
+ return uniqueSlug;
254
+ }
255
+
256
+ // src/storage/handlers.ts
257
+ var StorageValidationError = class extends Error {
258
+ constructor(message, code) {
259
+ super(message);
260
+ this.code = code;
261
+ this.name = "StorageValidationError";
262
+ }
263
+ };
264
+ async function handleCreatePage(adapter, input) {
265
+ if (!input.title.trim()) {
266
+ throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
267
+ }
268
+ const slug = input.slug?.trim() || generateSlug(input.title);
269
+ if (!slug) {
270
+ throw new StorageValidationError(
271
+ "Could not generate a valid slug from the provided title",
272
+ "INVALID_SLUG"
273
+ );
274
+ }
275
+ const existingPages = await adapter.listPages();
276
+ const existingSlugs = existingPages.map((p) => p.slug);
277
+ const uniqueSlug = ensureUniqueSlug(slug, existingSlugs);
278
+ const sanitizedSections = input.sections ? sanitizeSectionData(input.sections) : void 0;
279
+ return adapter.createPage({
280
+ ...input,
281
+ slug: uniqueSlug,
282
+ sections: sanitizedSections
283
+ });
284
+ }
285
+ async function handleUpdatePage(adapter, input) {
286
+ if (!input.id.trim()) {
287
+ throw new StorageValidationError("Page ID must not be empty", "EMPTY_ID");
288
+ }
289
+ if (input.title !== void 0 && !input.title.trim()) {
290
+ throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
291
+ }
292
+ if (input.slug !== void 0) {
293
+ const slug = input.slug.trim();
294
+ if (!slug) {
295
+ throw new StorageValidationError("Page slug must not be empty", "EMPTY_SLUG");
296
+ }
297
+ const existingPages = await adapter.listPages();
298
+ const existingSlugs = existingPages.filter((p) => p.id !== input.id).map((p) => p.slug);
299
+ if (existingSlugs.includes(slug)) {
300
+ throw new StorageValidationError(`Slug "${slug}" is already in use`, "DUPLICATE_SLUG");
301
+ }
302
+ }
303
+ const sanitizedInput = input.sections ? { ...input, sections: sanitizeSectionData(input.sections) } : input;
304
+ return adapter.updatePage(sanitizedInput);
305
+ }
306
+ async function handleDeletePage(adapter, id) {
307
+ if (!id.trim()) {
308
+ throw new StorageValidationError("Page ID must not be empty", "EMPTY_ID");
309
+ }
310
+ return adapter.deletePage(id);
311
+ }
312
+ async function handleCreateNavigation(adapter, input) {
313
+ if (!input.name.trim()) {
314
+ throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
315
+ }
316
+ const existingNavigations = await adapter.listNavigations();
317
+ const existingNames = existingNavigations.map((n) => n.name);
318
+ if (existingNames.includes(input.name.trim())) {
319
+ throw new StorageValidationError(
320
+ `Navigation name "${input.name.trim()}" is already in use`,
321
+ "DUPLICATE_NAME"
322
+ );
323
+ }
324
+ return adapter.createNavigation(input);
325
+ }
326
+ async function handleUpdateNavigation(adapter, input) {
327
+ if (!input.id.trim()) {
328
+ throw new StorageValidationError("Navigation ID must not be empty", "EMPTY_ID");
329
+ }
330
+ if (input.name !== void 0) {
331
+ const name = input.name.trim();
332
+ if (!name) {
333
+ throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
334
+ }
335
+ const existingNavigations = await adapter.listNavigations();
336
+ const existingNames = existingNavigations.filter((n) => n.id !== input.id).map((n) => n.name);
337
+ if (existingNames.includes(name)) {
338
+ throw new StorageValidationError(
339
+ `Navigation name "${name}" is already in use`,
340
+ "DUPLICATE_NAME"
341
+ );
342
+ }
343
+ }
344
+ return adapter.updateNavigation(input);
345
+ }
346
+ async function handleDeleteNavigation(adapter, id) {
347
+ if (!id.trim()) {
348
+ throw new StorageValidationError("Navigation ID must not be empty", "EMPTY_ID");
349
+ }
350
+ return adapter.deleteNavigation(id);
351
+ }
352
+
353
+ // src/next/factories.ts
354
+ function getResponseConstructor() {
355
+ const responseCtor = globalThis.Response;
356
+ if (!responseCtor) {
357
+ throw new Error("Response constructor is not available in this runtime");
358
+ }
359
+ return responseCtor;
360
+ }
361
+ function jsonResponse(data, status = 200) {
362
+ const ResponseCtor = getResponseConstructor();
363
+ return new ResponseCtor(JSON.stringify(data), {
364
+ status,
365
+ headers: {
366
+ "content-type": "application/json"
367
+ }
368
+ });
369
+ }
370
+ function getErrorMessage(error) {
371
+ if (error instanceof Error) {
372
+ return error.message;
373
+ }
374
+ return "Unknown error";
375
+ }
376
+ function errorResponse(error, fallbackStatus = 500) {
377
+ if (error instanceof StorageValidationError || error instanceof MediaValidationError || error instanceof SyntaxError) {
378
+ return jsonResponse({ error: getErrorMessage(error) }, 400);
379
+ }
380
+ return jsonResponse({ error: getErrorMessage(error) }, fallbackStatus);
381
+ }
382
+ async function resolveParams(context) {
383
+ return context.params;
384
+ }
385
+ function normalizeSlug(slug) {
386
+ return Array.isArray(slug) ? slug.join("/") : slug;
387
+ }
388
+ function asObject(value) {
389
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
390
+ return null;
391
+ }
392
+ return value;
393
+ }
394
+ function isFileLike(value) {
395
+ if (!value || typeof value !== "object") {
396
+ return false;
397
+ }
398
+ const candidate = value;
399
+ return typeof candidate.name === "string" && typeof candidate.type === "string" && typeof candidate.size === "number" && typeof candidate.arrayBuffer === "function";
400
+ }
401
+ function parseStringField(value) {
402
+ return typeof value === "string" ? value : void 0;
403
+ }
404
+ function parsePageSections(value) {
405
+ if (value === void 0) {
406
+ return void 0;
407
+ }
408
+ if (!Array.isArray(value)) {
409
+ return void 0;
410
+ }
411
+ const sections = [];
412
+ for (const entry of value) {
413
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
414
+ return void 0;
415
+ }
416
+ const sectionCandidate = entry;
417
+ if (sectionCandidate.id !== void 0 && typeof sectionCandidate.id !== "string" || typeof sectionCandidate.type !== "string" || !sectionCandidate.data || typeof sectionCandidate.data !== "object" || Array.isArray(sectionCandidate.data)) {
418
+ return void 0;
419
+ }
420
+ sections.push({
421
+ id: sectionCandidate.id,
422
+ type: sectionCandidate.type,
423
+ data: sectionCandidate.data
424
+ });
425
+ }
426
+ return sections;
427
+ }
428
+ function normalizePageSections(sections, existingSections = []) {
429
+ if (!sections) {
430
+ return void 0;
431
+ }
432
+ return sections.map((section, index) => ({
433
+ id: section.id ?? existingSections[index]?.id ?? `${section.type}-${index + 1}`,
434
+ type: section.type,
435
+ data: section.data
436
+ }));
437
+ }
438
+ function parseNavigationItems(value) {
439
+ if (!Array.isArray(value)) {
440
+ return void 0;
441
+ }
442
+ const items = [];
443
+ for (const entry of value) {
444
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
445
+ return void 0;
446
+ }
447
+ const itemCandidate = entry;
448
+ if (typeof itemCandidate.label !== "string" || typeof itemCandidate.href !== "string") {
449
+ return void 0;
450
+ }
451
+ const children = itemCandidate.children === void 0 ? void 0 : parseNavigationItems(itemCandidate.children);
452
+ if (itemCandidate.children !== void 0 && !children) {
453
+ return void 0;
454
+ }
455
+ items.push({
456
+ label: itemCandidate.label,
457
+ href: itemCandidate.href,
458
+ children
459
+ });
460
+ }
461
+ return items;
462
+ }
463
+ function parseCreatePageInput(payload) {
464
+ const pageType = parseStringField(payload.pageType);
465
+ const title = parseStringField(payload.title);
466
+ const slug = parseStringField(payload.slug);
467
+ const sections = normalizePageSections(parsePageSections(payload.sections));
468
+ if (!pageType || !title) {
469
+ return null;
470
+ }
471
+ if (payload.sections !== void 0 && !sections) {
472
+ return null;
473
+ }
474
+ return {
475
+ pageType,
476
+ title,
477
+ slug,
478
+ sections
479
+ };
480
+ }
481
+ function parseUpdatePagePatch(payload) {
482
+ const slug = parseStringField(payload.slug);
483
+ const pageType = parseStringField(payload.pageType);
484
+ const title = parseStringField(payload.title);
485
+ const sections = parsePageSections(payload.sections);
486
+ if (payload.slug !== void 0 && !slug) {
487
+ return null;
488
+ }
489
+ if (payload.pageType !== void 0 && !pageType) {
490
+ return null;
491
+ }
492
+ if (payload.title !== void 0 && !title) {
493
+ return null;
494
+ }
495
+ if (payload.sections !== void 0 && !sections) {
496
+ return null;
497
+ }
498
+ return {
499
+ slug,
500
+ pageType,
501
+ title,
502
+ sections
503
+ };
504
+ }
505
+ function parseCreateNavigationInput(payload) {
506
+ const name = parseStringField(payload.name);
507
+ const items = parseNavigationItems(payload.items);
508
+ if (!name || !items) {
509
+ return null;
510
+ }
511
+ return {
512
+ name,
513
+ items
514
+ };
515
+ }
516
+ function parseUpdateNavigationPatch(payload) {
517
+ const name = parseStringField(payload.name);
518
+ const items = payload.items === void 0 ? void 0 : parseNavigationItems(payload.items);
519
+ if (payload.name !== void 0 && !name) {
520
+ return null;
521
+ }
522
+ if (payload.items !== void 0 && !items) {
523
+ return null;
524
+ }
525
+ return {
526
+ name,
527
+ items
528
+ };
529
+ }
530
+ function toPageResponse2(page) {
531
+ return {
532
+ id: page.id,
533
+ slug: page.slug,
534
+ pageType: page.pageType,
535
+ title: page.title,
536
+ sections: page.sections,
537
+ meta: {
538
+ createdAt: page.createdAt.toISOString(),
539
+ updatedAt: page.updatedAt.toISOString()
540
+ }
541
+ };
542
+ }
543
+ function toNavigationResponse(navigation) {
544
+ return {
545
+ id: navigation.id,
546
+ name: navigation.name,
547
+ items: navigation.items,
548
+ meta: {
549
+ updatedAt: navigation.updatedAt.toISOString()
550
+ }
551
+ };
552
+ }
553
+ function createNextPagesRoute(config) {
554
+ return {
555
+ GET: async (_request) => {
556
+ try {
557
+ const pages = await handleListPages(config.storageAdapter, config.mediaAdapter);
558
+ return jsonResponse(pages);
559
+ } catch (error) {
560
+ return errorResponse(error);
561
+ }
562
+ },
563
+ POST: async (request) => {
564
+ try {
565
+ const payload = asObject(await request.json());
566
+ if (!payload) {
567
+ return jsonResponse({ error: "Request body must be an object" }, 400);
568
+ }
569
+ const createInput = parseCreatePageInput(payload);
570
+ if (!createInput) {
571
+ return jsonResponse({ error: "Invalid page payload" }, 400);
572
+ }
573
+ const page = await handleCreatePage(config.storageAdapter, createInput);
574
+ return jsonResponse(page, 201);
575
+ } catch (error) {
576
+ return errorResponse(error, 400);
577
+ }
578
+ }
579
+ };
580
+ }
581
+ function createNextPageBySlugRoute(config) {
582
+ return {
583
+ GET: async (_request, context) => {
584
+ try {
585
+ const { slug } = await resolveParams(context);
586
+ const page = await handleGetPageBySlug(
587
+ config.storageAdapter,
588
+ config.mediaAdapter,
589
+ normalizeSlug(slug)
590
+ );
591
+ if (!page) {
592
+ return jsonResponse({ error: "Page not found" }, 404);
593
+ }
594
+ return jsonResponse(page);
595
+ } catch (error) {
596
+ return errorResponse(error);
597
+ }
598
+ },
599
+ PUT: async (request, context) => {
600
+ try {
601
+ const { slug } = await resolveParams(context);
602
+ const normalizedSlug = normalizeSlug(slug);
603
+ const existingPage = await handleGetPageBySlug(
604
+ config.storageAdapter,
605
+ config.mediaAdapter,
606
+ normalizedSlug
607
+ );
608
+ if (!existingPage) {
609
+ return jsonResponse({ error: "Page not found" }, 404);
610
+ }
611
+ const payload = asObject(await request.json());
612
+ if (!payload) {
613
+ return jsonResponse({ error: "Request body must be an object" }, 400);
614
+ }
615
+ const pagePatch = parseUpdatePagePatch(payload);
616
+ if (!pagePatch) {
617
+ return jsonResponse({ error: "Invalid page payload" }, 400);
618
+ }
619
+ const normalizedSections = normalizePageSections(pagePatch.sections, existingPage.sections);
620
+ const page = await handleUpdatePage(config.storageAdapter, {
621
+ ...pagePatch,
622
+ sections: normalizedSections,
623
+ id: existingPage.id
624
+ });
625
+ return jsonResponse(toPageResponse2(page));
626
+ } catch (error) {
627
+ return errorResponse(error, 400);
628
+ }
629
+ },
630
+ DELETE: async (_request, context) => {
631
+ try {
632
+ const { slug } = await resolveParams(context);
633
+ const existingPage = await handleGetPageBySlug(
634
+ config.storageAdapter,
635
+ config.mediaAdapter,
636
+ normalizeSlug(slug)
637
+ );
638
+ if (!existingPage) {
639
+ return jsonResponse({ error: "Page not found" }, 404);
640
+ }
641
+ await handleDeletePage(config.storageAdapter, existingPage.id);
642
+ return jsonResponse({ success: true });
643
+ } catch (error) {
644
+ return errorResponse(error);
645
+ }
646
+ }
647
+ };
648
+ }
649
+ function createNextPageByIdRoute(config) {
650
+ return {
651
+ GET: async (_request, context) => {
652
+ try {
653
+ const { id } = await resolveParams(context);
654
+ const page = await config.storageAdapter.getPageById(id);
655
+ if (!page) {
656
+ return jsonResponse({ error: "Page not found" }, 404);
657
+ }
658
+ return jsonResponse(toPageResponse2(page));
659
+ } catch (error) {
660
+ return errorResponse(error);
661
+ }
662
+ },
663
+ PUT: async (request, context) => {
664
+ try {
665
+ const { id } = await resolveParams(context);
666
+ const existingPage = await config.storageAdapter.getPageById(id);
667
+ if (!existingPage) {
668
+ return jsonResponse({ error: "Page not found" }, 404);
669
+ }
670
+ const payload = asObject(await request.json());
671
+ if (!payload) {
672
+ return jsonResponse({ error: "Request body must be an object" }, 400);
673
+ }
674
+ const pagePatch = parseUpdatePagePatch(payload);
675
+ if (!pagePatch) {
676
+ return jsonResponse({ error: "Invalid page payload" }, 400);
677
+ }
678
+ const normalizedSections = normalizePageSections(pagePatch.sections, existingPage.sections);
679
+ const page = await handleUpdatePage(config.storageAdapter, {
680
+ ...pagePatch,
681
+ sections: normalizedSections,
682
+ id: existingPage.id
683
+ });
684
+ return jsonResponse(toPageResponse2(page));
685
+ } catch (error) {
686
+ return errorResponse(error, 400);
687
+ }
688
+ },
689
+ DELETE: async (_request, context) => {
690
+ try {
691
+ const { id } = await resolveParams(context);
692
+ const existingPage = await config.storageAdapter.getPageById(id);
693
+ if (!existingPage) {
694
+ return jsonResponse({ error: "Page not found" }, 404);
695
+ }
696
+ await handleDeletePage(config.storageAdapter, existingPage.id);
697
+ return jsonResponse({ success: true });
698
+ } catch (error) {
699
+ return errorResponse(error);
700
+ }
701
+ }
702
+ };
703
+ }
704
+ function createNextMediaRoute(config) {
705
+ return {
706
+ GET: async (_request) => {
707
+ try {
708
+ const media = await handleListMedia(config.mediaAdapter);
709
+ return jsonResponse(media);
710
+ } catch (error) {
711
+ return errorResponse(error);
712
+ }
713
+ },
714
+ POST: async (request) => {
715
+ try {
716
+ const formData = await request.formData();
717
+ const file = formData.get("file");
718
+ if (!isFileLike(file)) {
719
+ return jsonResponse({ error: "No file provided" }, 400);
720
+ }
721
+ const input = {
722
+ filename: file.name,
723
+ mimeType: file.type,
724
+ size: file.size,
725
+ data: await file.arrayBuffer()
726
+ };
727
+ const mediaFile = await handleUploadMedia(config.mediaAdapter, input);
728
+ return jsonResponse(mediaFile, 201);
729
+ } catch (error) {
730
+ return errorResponse(error, 400);
731
+ }
732
+ }
733
+ };
734
+ }
735
+ function createNextMediaByIdRoute(config) {
736
+ return {
737
+ GET: async (_request, context) => {
738
+ try {
739
+ const { id } = await resolveParams(context);
740
+ const media = await handleGetMedia(config.mediaAdapter, id);
741
+ if (!media) {
742
+ return jsonResponse({ error: "Media not found" }, 404);
743
+ }
744
+ return jsonResponse(media);
745
+ } catch (error) {
746
+ return errorResponse(error);
747
+ }
748
+ },
749
+ DELETE: async (_request, context) => {
750
+ try {
751
+ const { id } = await resolveParams(context);
752
+ await handleDeleteMedia(config.mediaAdapter, id);
753
+ return jsonResponse({ success: true });
754
+ } catch (error) {
755
+ return errorResponse(error);
756
+ }
757
+ }
758
+ };
759
+ }
760
+ function createNextNavigationRoute(config) {
761
+ return {
762
+ GET: async (_request) => {
763
+ try {
764
+ const navigations = await config.storageAdapter.listNavigations();
765
+ return jsonResponse(navigations.map(toNavigationResponse));
766
+ } catch (error) {
767
+ return errorResponse(error);
768
+ }
769
+ },
770
+ POST: async (request) => {
771
+ try {
772
+ const payload = asObject(await request.json());
773
+ if (!payload) {
774
+ return jsonResponse({ error: "Request body must be an object" }, 400);
775
+ }
776
+ const createInput = parseCreateNavigationInput(payload);
777
+ if (!createInput) {
778
+ return jsonResponse({ error: "Invalid navigation payload" }, 400);
779
+ }
780
+ const navigation = await handleCreateNavigation(config.storageAdapter, createInput);
781
+ return jsonResponse(navigation, 201);
782
+ } catch (error) {
783
+ return errorResponse(error, 400);
784
+ }
785
+ }
786
+ };
787
+ }
788
+ function createNextNavigationByIdRoute(config) {
789
+ return {
790
+ GET: async (_request, context) => {
791
+ try {
792
+ const { id } = await resolveParams(context);
793
+ const navigation = await config.storageAdapter.getNavigationById(id);
794
+ if (!navigation) {
795
+ return jsonResponse({ error: "Navigation not found" }, 404);
796
+ }
797
+ return jsonResponse(toNavigationResponse(navigation));
798
+ } catch (error) {
799
+ return errorResponse(error);
800
+ }
801
+ },
802
+ PUT: async (request, context) => {
803
+ try {
804
+ const { id } = await resolveParams(context);
805
+ const existingNavigation = await config.storageAdapter.getNavigationById(id);
806
+ if (!existingNavigation) {
807
+ return jsonResponse({ error: "Navigation not found" }, 404);
808
+ }
809
+ const payload = asObject(await request.json());
810
+ if (!payload) {
811
+ return jsonResponse({ error: "Request body must be an object" }, 400);
812
+ }
813
+ const navigationPatch = parseUpdateNavigationPatch(payload);
814
+ if (!navigationPatch) {
815
+ return jsonResponse({ error: "Invalid navigation payload" }, 400);
816
+ }
817
+ const navigation = await handleUpdateNavigation(config.storageAdapter, {
818
+ ...navigationPatch,
819
+ id: existingNavigation.id
820
+ });
821
+ return jsonResponse(navigation);
822
+ } catch (error) {
823
+ return errorResponse(error, 400);
824
+ }
825
+ },
826
+ DELETE: async (_request, context) => {
827
+ try {
828
+ const { id } = await resolveParams(context);
829
+ const existingNavigation = await config.storageAdapter.getNavigationById(id);
830
+ if (!existingNavigation) {
831
+ return jsonResponse({ error: "Navigation not found" }, 404);
832
+ }
833
+ await handleDeleteNavigation(config.storageAdapter, existingNavigation.id);
834
+ return jsonResponse({ success: true });
835
+ } catch (error) {
836
+ return errorResponse(error);
837
+ }
838
+ }
839
+ };
840
+ }
841
+
842
+ // src/auth/handlers.ts
843
+ var AuthValidationError = class extends Error {
844
+ constructor(message) {
845
+ super(message);
846
+ this.name = "AuthValidationError";
847
+ }
848
+ };
849
+ async function handleSignInWithOAuth(adapter, input) {
850
+ if (!input.provider) {
851
+ throw new AuthValidationError("Provider is required");
852
+ }
853
+ const validProviders = ["google", "github", "gitlab", "azure", "bitbucket"];
854
+ if (!validProviders.includes(input.provider)) {
855
+ throw new AuthValidationError(`Invalid provider. Must be one of: ${validProviders.join(", ")}`);
856
+ }
857
+ return await adapter.signInWithOAuth(input);
858
+ }
859
+ function validatePassword(password) {
860
+ if (password.length < 8) {
861
+ throw new AuthValidationError("Password must be at least 8 characters");
862
+ }
863
+ const hasUpperCase = /[A-Z]/.test(password);
864
+ const hasLowerCase = /[a-z]/.test(password);
865
+ const hasNumbers = /\d/.test(password);
866
+ const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>_\-+=\[\]\\/'`~]/.test(password);
867
+ const complexityCount = [hasUpperCase, hasLowerCase, hasNumbers, hasSpecialChar].filter(
868
+ Boolean
869
+ ).length;
870
+ if (complexityCount < 3) {
871
+ throw new AuthValidationError(
872
+ "Password must contain at least 3 of: uppercase letters, lowercase letters, numbers, special characters"
873
+ );
874
+ }
875
+ const commonPasswords = [
876
+ "password",
877
+ "password1",
878
+ "password123",
879
+ "12345678",
880
+ "123456789",
881
+ "qwerty",
882
+ "abc123",
883
+ "monkey",
884
+ "1234567890",
885
+ "letmein",
886
+ "trustno1",
887
+ "dragon",
888
+ "baseball",
889
+ "iloveyou",
890
+ "master",
891
+ "sunshine",
892
+ "ashley",
893
+ "bailey",
894
+ "shadow",
895
+ "123123"
896
+ ];
897
+ if (commonPasswords.includes(password.toLowerCase())) {
898
+ throw new AuthValidationError("Password is too common. Please choose a stronger password.");
899
+ }
900
+ }
901
+ async function handleSignInWithPassword(adapter, input) {
902
+ if (!input.email || !input.password) {
903
+ throw new AuthValidationError("Email and password are required");
904
+ }
905
+ if (!input.email.includes("@")) {
906
+ throw new AuthValidationError("Invalid email format");
907
+ }
908
+ validatePassword(input.password);
909
+ return await adapter.signInWithPassword(input);
910
+ }
911
+ async function handleSignOut(adapter, accessToken) {
912
+ if (!accessToken) {
913
+ throw new AuthValidationError("Access token is required");
914
+ }
915
+ return await adapter.signOut(accessToken);
916
+ }
917
+ async function handleVerifySession(adapter, input) {
918
+ if (!input.accessToken) {
919
+ throw new AuthValidationError("Access token is required");
920
+ }
921
+ if (input.expiresAt) {
922
+ const now = /* @__PURE__ */ new Date();
923
+ const expiresAt = input.expiresAt instanceof Date ? input.expiresAt : new Date(input.expiresAt);
924
+ if (now > expiresAt) {
925
+ throw new AuthValidationError("Token has expired");
926
+ }
927
+ }
928
+ const user = await adapter.verifySession(input);
929
+ if (!user) {
930
+ throw new AuthValidationError("Invalid or expired token");
931
+ }
932
+ return user;
933
+ }
934
+ async function handleRefreshSession(adapter, refreshToken) {
935
+ if (!refreshToken) {
936
+ throw new AuthValidationError("Refresh token is required");
937
+ }
938
+ return await adapter.refreshSession(refreshToken);
939
+ }
940
+ async function handleGetCurrentUser(adapter, accessToken) {
941
+ if (!accessToken) {
942
+ throw new AuthValidationError("Access token is required");
943
+ }
944
+ return await adapter.getCurrentUser(accessToken);
945
+ }
946
+
947
+ // src/next/auth-factories.ts
948
+ function extractBearerToken(request) {
949
+ const authHeader = request.headers.get("authorization") ?? request.headers.get("Authorization");
950
+ if (!authHeader) return null;
951
+ const parts = authHeader.split(" ");
952
+ if (parts.length !== 2 || parts[0] !== "Bearer") return null;
953
+ return parts[1] ?? null;
954
+ }
955
+ function createNextAuthOAuthRoute(config, Response) {
956
+ return async function POST(request) {
957
+ try {
958
+ const body = await request.json();
959
+ const input = body;
960
+ const result = await handleSignInWithOAuth(config.authAdapter, input);
961
+ return Response.json({ data: result }, { status: 200 });
962
+ } catch (err) {
963
+ if (err instanceof AuthValidationError) {
964
+ return Response.json(
965
+ { error: { message: err.message, code: "VALIDATION_ERROR" } },
966
+ { status: 400 }
967
+ );
968
+ }
969
+ const message = err instanceof Error ? err.message : "OAuth initialization failed";
970
+ return Response.json({ error: { message, code: "OAUTH_ERROR" } }, { status: 500 });
971
+ }
972
+ };
973
+ }
974
+ function createNextAuthSignInRoute(config, Response) {
975
+ return async function POST(request) {
976
+ try {
977
+ const body = await request.json();
978
+ const input = body;
979
+ const session = await handleSignInWithPassword(config.authAdapter, input);
980
+ return Response.json(session, { status: 200 });
981
+ } catch (err) {
982
+ if (err instanceof AuthValidationError) {
983
+ return Response.json(
984
+ { error: { message: err.message, code: "VALIDATION_ERROR" } },
985
+ { status: 400 }
986
+ );
987
+ }
988
+ const message = err instanceof Error ? err.message : "Sign in failed";
989
+ return Response.json({ error: { message, code: "AUTH_ERROR" } }, { status: 401 });
990
+ }
991
+ };
992
+ }
993
+ function createNextAuthSignOutRoute(config, Response) {
994
+ return async function POST(request) {
995
+ try {
996
+ const token = extractBearerToken(request);
997
+ if (!token) {
998
+ return Response.json(
999
+ { error: { message: "No token provided", code: "NO_TOKEN" } },
1000
+ { status: 401 }
1001
+ );
1002
+ }
1003
+ await handleSignOut(config.authAdapter, token);
1004
+ return Response.json({ message: "Signed out successfully" }, { status: 200 });
1005
+ } catch (error) {
1006
+ const message = error instanceof Error ? error.message : "Sign out failed";
1007
+ return Response.json({ error: { message, code: "SIGNOUT_ERROR" } }, { status: 500 });
1008
+ }
1009
+ };
1010
+ }
1011
+ function createNextAuthVerifyRoute(config, Response) {
1012
+ return async function POST(request) {
1013
+ try {
1014
+ const token = extractBearerToken(request);
1015
+ if (!token) {
1016
+ return Response.json(
1017
+ { error: { message: "No token provided", code: "NO_TOKEN" } },
1018
+ { status: 401 }
1019
+ );
1020
+ }
1021
+ const user = await handleVerifySession(config.authAdapter, { accessToken: token });
1022
+ if (!user) {
1023
+ return Response.json(
1024
+ { error: { message: "Invalid token", code: "INVALID_TOKEN" } },
1025
+ { status: 401 }
1026
+ );
1027
+ }
1028
+ return Response.json(user, { status: 200 });
1029
+ } catch (error) {
1030
+ const message = error instanceof Error ? error.message : "Verification failed";
1031
+ return Response.json({ error: { message, code: "VERIFY_ERROR" } }, { status: 401 });
1032
+ }
1033
+ };
1034
+ }
1035
+ function createNextAuthRefreshRoute(config, Response) {
1036
+ return async function POST(request) {
1037
+ try {
1038
+ const body = await request.json();
1039
+ const { refreshToken } = body;
1040
+ if (!refreshToken) {
1041
+ return Response.json(
1042
+ { error: { message: "Refresh token required", code: "NO_REFRESH_TOKEN" } },
1043
+ { status: 400 }
1044
+ );
1045
+ }
1046
+ const session = await handleRefreshSession(config.authAdapter, refreshToken);
1047
+ return Response.json(session, { status: 200 });
1048
+ } catch (error) {
1049
+ const message = error instanceof Error ? error.message : "Refresh failed";
1050
+ return Response.json({ error: { message, code: "REFRESH_ERROR" } }, { status: 401 });
1051
+ }
1052
+ };
1053
+ }
1054
+ function createNextAuthCurrentUserRoute(config, Response) {
1055
+ return async function GET(request) {
1056
+ try {
1057
+ const token = extractBearerToken(request);
1058
+ if (!token) {
1059
+ return Response.json(
1060
+ { error: { message: "No token provided", code: "NO_TOKEN" } },
1061
+ { status: 401 }
1062
+ );
1063
+ }
1064
+ const user = await handleGetCurrentUser(config.authAdapter, token);
1065
+ if (!user) {
1066
+ return Response.json(
1067
+ { error: { message: "User not found", code: "USER_NOT_FOUND" } },
1068
+ { status: 401 }
1069
+ );
1070
+ }
1071
+ return Response.json(user, { status: 200 });
1072
+ } catch (error) {
1073
+ const message = error instanceof Error ? error.message : "Failed to get user";
1074
+ return Response.json({ error: { message, code: "GET_USER_ERROR" } }, { status: 500 });
1075
+ }
1076
+ };
1077
+ }
1078
+ function createAuthenticatedRoute(authAdapter, Response, handler) {
1079
+ return async (request) => {
1080
+ const token = extractBearerToken(request);
1081
+ if (!token) {
1082
+ return Response.json(
1083
+ { error: { message: "Authentication required", code: "NO_TOKEN" } },
1084
+ { status: 401 }
1085
+ );
1086
+ }
1087
+ const user = await handleVerifySession(authAdapter, { accessToken: token });
1088
+ if (!user) {
1089
+ return Response.json(
1090
+ { error: { message: "Invalid or expired token", code: "INVALID_TOKEN" } },
1091
+ { status: 401 }
1092
+ );
1093
+ }
1094
+ return handler(request, user);
1095
+ };
1096
+ }
1097
+ // Annotate the CommonJS export names for ESM import in node:
1098
+ 0 && (module.exports = {
1099
+ createAuthenticatedRoute,
1100
+ createNextAuthCurrentUserRoute,
1101
+ createNextAuthOAuthRoute,
1102
+ createNextAuthRefreshRoute,
1103
+ createNextAuthSignInRoute,
1104
+ createNextAuthSignOutRoute,
1105
+ createNextAuthVerifyRoute,
1106
+ createNextMediaByIdRoute,
1107
+ createNextMediaRoute,
1108
+ createNextNavigationByIdRoute,
1109
+ createNextNavigationRoute,
1110
+ createNextPageByIdRoute,
1111
+ createNextPageBySlugRoute,
1112
+ createNextPagesRoute
1113
+ });
1114
+ //# sourceMappingURL=index.cjs.map