@structcms/api 0.1.0 → 0.1.1
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 +2 -2
- package/dist/index.cjs +266 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +241 -12
- package/dist/index.d.ts +241 -12
- package/dist/index.js +250 -20
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +147 -33
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +3 -2
- package/dist/next/index.d.ts +3 -2
- package/dist/next/index.js +147 -33
- package/dist/next/index.js.map +1 -1
- package/dist/supabase/index.cjs +37 -1
- package/dist/supabase/index.cjs.map +1 -1
- package/dist/supabase/index.d.cts +1 -1
- package/dist/supabase/index.d.ts +1 -1
- package/dist/supabase/index.js +37 -1
- package/dist/supabase/index.js.map +1 -1
- package/dist/{types-Zi0Iyow1.d.cts → types-Cdui_Ets.d.cts} +21 -2
- package/dist/{types-Zi0Iyow1.d.ts → types-Cdui_Ets.d.ts} +21 -2
- package/package.json +3 -2
package/dist/next/index.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
// src/media/resolve.ts
|
|
2
2
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
-
var MEDIA_FIELD_SUFFIXES = [
|
|
3
|
+
var MEDIA_FIELD_SUFFIXES = [
|
|
4
|
+
"_image",
|
|
5
|
+
"_media",
|
|
6
|
+
"_photo",
|
|
7
|
+
"_thumbnail",
|
|
8
|
+
"_avatar",
|
|
9
|
+
"_icon",
|
|
10
|
+
"_file",
|
|
11
|
+
"_document",
|
|
12
|
+
"_attachment",
|
|
13
|
+
"_download"
|
|
14
|
+
];
|
|
4
15
|
function isMediaField(fieldName) {
|
|
5
16
|
const lower = fieldName.toLowerCase();
|
|
6
|
-
if (lower === "image" || lower === "media" || lower === "photo" || lower === "thumbnail" || lower === "avatar" || lower === "icon") {
|
|
17
|
+
if (lower === "image" || lower === "media" || lower === "photo" || lower === "thumbnail" || lower === "avatar" || lower === "icon" || lower === "file" || lower === "document" || lower === "attachment" || lower === "download") {
|
|
7
18
|
return true;
|
|
8
19
|
}
|
|
9
20
|
return MEDIA_FIELD_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
@@ -74,6 +85,7 @@ async function handleGetPageBySlug(adapter, mediaAdapter, slug) {
|
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
// src/media/types.ts
|
|
88
|
+
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
77
89
|
var ALLOWED_MIME_TYPES = [
|
|
78
90
|
"image/jpeg",
|
|
79
91
|
"image/png",
|
|
@@ -81,6 +93,23 @@ var ALLOWED_MIME_TYPES = [
|
|
|
81
93
|
"image/webp",
|
|
82
94
|
"image/svg+xml"
|
|
83
95
|
];
|
|
96
|
+
var ALLOWED_DOCUMENT_MIME_TYPES = [
|
|
97
|
+
"application/pdf",
|
|
98
|
+
"application/msword",
|
|
99
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
100
|
+
"application/vnd.ms-excel",
|
|
101
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
102
|
+
"application/vnd.ms-powerpoint",
|
|
103
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
104
|
+
"text/plain",
|
|
105
|
+
"text/csv",
|
|
106
|
+
"application/zip",
|
|
107
|
+
"application/gzip"
|
|
108
|
+
];
|
|
109
|
+
var ALL_ALLOWED_MIME_TYPES = [
|
|
110
|
+
...ALLOWED_MIME_TYPES,
|
|
111
|
+
...ALLOWED_DOCUMENT_MIME_TYPES
|
|
112
|
+
];
|
|
84
113
|
|
|
85
114
|
// src/media/handlers.ts
|
|
86
115
|
var MediaValidationError = class extends Error {
|
|
@@ -91,16 +120,25 @@ var MediaValidationError = class extends Error {
|
|
|
91
120
|
}
|
|
92
121
|
};
|
|
93
122
|
function validateMimeType(mimeType) {
|
|
94
|
-
const allowed =
|
|
123
|
+
const allowed = ALL_ALLOWED_MIME_TYPES;
|
|
95
124
|
if (!allowed.includes(mimeType)) {
|
|
96
125
|
throw new MediaValidationError(
|
|
97
|
-
`Invalid file type: ${mimeType}. Allowed types: ${
|
|
126
|
+
`Invalid file type: ${mimeType}. Allowed types: ${ALL_ALLOWED_MIME_TYPES.join(", ")}`,
|
|
98
127
|
"INVALID_MIME_TYPE"
|
|
99
128
|
);
|
|
100
129
|
}
|
|
101
130
|
}
|
|
131
|
+
function validateFileSize(size) {
|
|
132
|
+
if (size > MAX_FILE_SIZE) {
|
|
133
|
+
throw new MediaValidationError(
|
|
134
|
+
`File size exceeds maximum allowed size of ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
|
135
|
+
"FILE_TOO_LARGE"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
102
139
|
async function handleUploadMedia(adapter, input) {
|
|
103
140
|
validateMimeType(input.mimeType);
|
|
141
|
+
validateFileSize(input.size);
|
|
104
142
|
return adapter.upload(input);
|
|
105
143
|
}
|
|
106
144
|
async function handleGetMedia(adapter, id) {
|
|
@@ -147,6 +185,12 @@ var SANITIZE_OPTIONS = {
|
|
|
147
185
|
function sanitizeString(value) {
|
|
148
186
|
return sanitizeHtml(value, SANITIZE_OPTIONS);
|
|
149
187
|
}
|
|
188
|
+
function stripTags(value) {
|
|
189
|
+
return sanitizeHtml(value, {
|
|
190
|
+
allowedTags: [],
|
|
191
|
+
allowedAttributes: {}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
150
194
|
function sanitizeValue(value) {
|
|
151
195
|
if (typeof value === "string") {
|
|
152
196
|
return sanitizeString(value);
|
|
@@ -213,10 +257,11 @@ var StorageValidationError = class extends Error {
|
|
|
213
257
|
}
|
|
214
258
|
};
|
|
215
259
|
async function handleCreatePage(adapter, input) {
|
|
216
|
-
|
|
260
|
+
const sanitizedTitle = stripTags(input.title).trim();
|
|
261
|
+
if (!sanitizedTitle) {
|
|
217
262
|
throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
|
|
218
263
|
}
|
|
219
|
-
const slug = input.slug?.trim() || generateSlug(
|
|
264
|
+
const slug = input.slug?.trim() || generateSlug(sanitizedTitle);
|
|
220
265
|
if (!slug) {
|
|
221
266
|
throw new StorageValidationError(
|
|
222
267
|
"Could not generate a valid slug from the provided title",
|
|
@@ -229,6 +274,7 @@ async function handleCreatePage(adapter, input) {
|
|
|
229
274
|
const sanitizedSections = input.sections ? sanitizeSectionData(input.sections) : void 0;
|
|
230
275
|
return adapter.createPage({
|
|
231
276
|
...input,
|
|
277
|
+
title: sanitizedTitle,
|
|
232
278
|
slug: uniqueSlug,
|
|
233
279
|
sections: sanitizedSections
|
|
234
280
|
});
|
|
@@ -237,8 +283,12 @@ async function handleUpdatePage(adapter, input) {
|
|
|
237
283
|
if (!input.id.trim()) {
|
|
238
284
|
throw new StorageValidationError("Page ID must not be empty", "EMPTY_ID");
|
|
239
285
|
}
|
|
240
|
-
|
|
241
|
-
|
|
286
|
+
let sanitizedTitle;
|
|
287
|
+
if (input.title !== void 0) {
|
|
288
|
+
sanitizedTitle = stripTags(input.title).trim();
|
|
289
|
+
if (!sanitizedTitle) {
|
|
290
|
+
throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
|
|
291
|
+
}
|
|
242
292
|
}
|
|
243
293
|
if (input.slug !== void 0) {
|
|
244
294
|
const slug = input.slug.trim();
|
|
@@ -251,7 +301,11 @@ async function handleUpdatePage(adapter, input) {
|
|
|
251
301
|
throw new StorageValidationError(`Slug "${slug}" is already in use`, "DUPLICATE_SLUG");
|
|
252
302
|
}
|
|
253
303
|
}
|
|
254
|
-
const sanitizedInput =
|
|
304
|
+
const sanitizedInput = {
|
|
305
|
+
...input,
|
|
306
|
+
...sanitizedTitle !== void 0 && { title: sanitizedTitle },
|
|
307
|
+
...input.sections && { sections: sanitizeSectionData(input.sections) }
|
|
308
|
+
};
|
|
255
309
|
return adapter.updatePage(sanitizedInput);
|
|
256
310
|
}
|
|
257
311
|
async function handleDeletePage(adapter, id) {
|
|
@@ -261,38 +315,46 @@ async function handleDeletePage(adapter, id) {
|
|
|
261
315
|
return adapter.deletePage(id);
|
|
262
316
|
}
|
|
263
317
|
async function handleCreateNavigation(adapter, input) {
|
|
264
|
-
|
|
318
|
+
const sanitizedName = stripTags(input.name).trim();
|
|
319
|
+
if (!sanitizedName) {
|
|
265
320
|
throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
|
|
266
321
|
}
|
|
267
322
|
const existingNavigations = await adapter.listNavigations();
|
|
268
323
|
const existingNames = existingNavigations.map((n) => n.name);
|
|
269
|
-
if (existingNames.includes(
|
|
324
|
+
if (existingNames.includes(sanitizedName)) {
|
|
270
325
|
throw new StorageValidationError(
|
|
271
|
-
`Navigation name "${
|
|
326
|
+
`Navigation name "${sanitizedName}" is already in use`,
|
|
272
327
|
"DUPLICATE_NAME"
|
|
273
328
|
);
|
|
274
329
|
}
|
|
275
|
-
return adapter.createNavigation(
|
|
330
|
+
return adapter.createNavigation({
|
|
331
|
+
...input,
|
|
332
|
+
name: sanitizedName
|
|
333
|
+
});
|
|
276
334
|
}
|
|
277
335
|
async function handleUpdateNavigation(adapter, input) {
|
|
278
336
|
if (!input.id.trim()) {
|
|
279
337
|
throw new StorageValidationError("Navigation ID must not be empty", "EMPTY_ID");
|
|
280
338
|
}
|
|
339
|
+
let sanitizedName;
|
|
281
340
|
if (input.name !== void 0) {
|
|
282
|
-
|
|
283
|
-
if (!
|
|
341
|
+
sanitizedName = stripTags(input.name).trim();
|
|
342
|
+
if (!sanitizedName) {
|
|
284
343
|
throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
|
|
285
344
|
}
|
|
286
345
|
const existingNavigations = await adapter.listNavigations();
|
|
287
346
|
const existingNames = existingNavigations.filter((n) => n.id !== input.id).map((n) => n.name);
|
|
288
|
-
if (existingNames.includes(
|
|
347
|
+
if (existingNames.includes(sanitizedName)) {
|
|
289
348
|
throw new StorageValidationError(
|
|
290
|
-
`Navigation name "${
|
|
349
|
+
`Navigation name "${sanitizedName}" is already in use`,
|
|
291
350
|
"DUPLICATE_NAME"
|
|
292
351
|
);
|
|
293
352
|
}
|
|
294
353
|
}
|
|
295
|
-
return adapter.updateNavigation(
|
|
354
|
+
return adapter.updateNavigation({
|
|
355
|
+
...input,
|
|
356
|
+
...sanitizedName !== void 0 && { name: sanitizedName }
|
|
357
|
+
});
|
|
296
358
|
}
|
|
297
359
|
async function handleDeleteNavigation(adapter, id) {
|
|
298
360
|
if (!id.trim()) {
|
|
@@ -328,7 +390,7 @@ function errorResponse(error, fallbackStatus = 500) {
|
|
|
328
390
|
if (error instanceof StorageValidationError || error instanceof MediaValidationError || error instanceof SyntaxError) {
|
|
329
391
|
return jsonResponse({ error: getErrorMessage(error) }, 400);
|
|
330
392
|
}
|
|
331
|
-
return jsonResponse({ error:
|
|
393
|
+
return jsonResponse({ error: "Internal Server Error" }, fallbackStatus);
|
|
332
394
|
}
|
|
333
395
|
async function resolveParams(context) {
|
|
334
396
|
return context.params;
|
|
@@ -652,11 +714,51 @@ function createNextPageByIdRoute(config) {
|
|
|
652
714
|
}
|
|
653
715
|
};
|
|
654
716
|
}
|
|
717
|
+
function getQueryParam(url, name) {
|
|
718
|
+
const queryStart = url.indexOf("?");
|
|
719
|
+
if (queryStart === -1) return null;
|
|
720
|
+
for (const pair of url.slice(queryStart + 1).split("&")) {
|
|
721
|
+
const eqIndex = pair.indexOf("=");
|
|
722
|
+
const key = decodeURIComponent(eqIndex === -1 ? pair : pair.slice(0, eqIndex));
|
|
723
|
+
if (key === name) return eqIndex === -1 ? "" : decodeURIComponent(pair.slice(eqIndex + 1));
|
|
724
|
+
}
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
function parseMediaFilter(request) {
|
|
728
|
+
if (!request.url) {
|
|
729
|
+
return void 0;
|
|
730
|
+
}
|
|
731
|
+
const category = getQueryParam(request.url, "category");
|
|
732
|
+
const limit = getQueryParam(request.url, "limit");
|
|
733
|
+
const offset = getQueryParam(request.url, "offset");
|
|
734
|
+
const filter = {};
|
|
735
|
+
let hasFilter = false;
|
|
736
|
+
if (category === "image" || category === "document") {
|
|
737
|
+
filter.category = category;
|
|
738
|
+
hasFilter = true;
|
|
739
|
+
}
|
|
740
|
+
if (limit) {
|
|
741
|
+
const parsed = Number.parseInt(limit, 10);
|
|
742
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
743
|
+
filter.limit = parsed;
|
|
744
|
+
hasFilter = true;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (offset) {
|
|
748
|
+
const parsed = Number.parseInt(offset, 10);
|
|
749
|
+
if (!Number.isNaN(parsed) && parsed >= 0) {
|
|
750
|
+
filter.offset = parsed;
|
|
751
|
+
hasFilter = true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return hasFilter ? filter : void 0;
|
|
755
|
+
}
|
|
655
756
|
function createNextMediaRoute(config) {
|
|
656
757
|
return {
|
|
657
|
-
GET: async (
|
|
758
|
+
GET: async (request) => {
|
|
658
759
|
try {
|
|
659
|
-
const
|
|
760
|
+
const filter = parseMediaFilter(request);
|
|
761
|
+
const media = await handleListMedia(config.mediaAdapter, filter);
|
|
660
762
|
return jsonResponse(media);
|
|
661
763
|
} catch (error) {
|
|
662
764
|
return errorResponse(error);
|
|
@@ -917,8 +1019,10 @@ function createNextAuthOAuthRoute(config, Response) {
|
|
|
917
1019
|
{ status: 400 }
|
|
918
1020
|
);
|
|
919
1021
|
}
|
|
920
|
-
|
|
921
|
-
|
|
1022
|
+
return Response.json(
|
|
1023
|
+
{ error: { message: "Internal Server Error", code: "OAUTH_ERROR" } },
|
|
1024
|
+
{ status: 500 }
|
|
1025
|
+
);
|
|
922
1026
|
}
|
|
923
1027
|
};
|
|
924
1028
|
}
|
|
@@ -936,8 +1040,10 @@ function createNextAuthSignInRoute(config, Response) {
|
|
|
936
1040
|
{ status: 400 }
|
|
937
1041
|
);
|
|
938
1042
|
}
|
|
939
|
-
|
|
940
|
-
|
|
1043
|
+
return Response.json(
|
|
1044
|
+
{ error: { message: "Invalid credentials", code: "AUTH_ERROR" } },
|
|
1045
|
+
{ status: 401 }
|
|
1046
|
+
);
|
|
941
1047
|
}
|
|
942
1048
|
};
|
|
943
1049
|
}
|
|
@@ -954,8 +1060,10 @@ function createNextAuthSignOutRoute(config, Response) {
|
|
|
954
1060
|
await handleSignOut(config.authAdapter, token);
|
|
955
1061
|
return Response.json({ message: "Signed out successfully" }, { status: 200 });
|
|
956
1062
|
} catch (error) {
|
|
957
|
-
|
|
958
|
-
|
|
1063
|
+
return Response.json(
|
|
1064
|
+
{ error: { message: "Internal Server Error", code: "SIGNOUT_ERROR" } },
|
|
1065
|
+
{ status: 500 }
|
|
1066
|
+
);
|
|
959
1067
|
}
|
|
960
1068
|
};
|
|
961
1069
|
}
|
|
@@ -978,8 +1086,10 @@ function createNextAuthVerifyRoute(config, Response) {
|
|
|
978
1086
|
}
|
|
979
1087
|
return Response.json(user, { status: 200 });
|
|
980
1088
|
} catch (error) {
|
|
981
|
-
|
|
982
|
-
|
|
1089
|
+
return Response.json(
|
|
1090
|
+
{ error: { message: "Authentication failed", code: "VERIFY_ERROR" } },
|
|
1091
|
+
{ status: 401 }
|
|
1092
|
+
);
|
|
983
1093
|
}
|
|
984
1094
|
};
|
|
985
1095
|
}
|
|
@@ -997,8 +1107,10 @@ function createNextAuthRefreshRoute(config, Response) {
|
|
|
997
1107
|
const session = await handleRefreshSession(config.authAdapter, refreshToken);
|
|
998
1108
|
return Response.json(session, { status: 200 });
|
|
999
1109
|
} catch (error) {
|
|
1000
|
-
|
|
1001
|
-
|
|
1110
|
+
return Response.json(
|
|
1111
|
+
{ error: { message: "Session refresh failed", code: "REFRESH_ERROR" } },
|
|
1112
|
+
{ status: 401 }
|
|
1113
|
+
);
|
|
1002
1114
|
}
|
|
1003
1115
|
};
|
|
1004
1116
|
}
|
|
@@ -1021,8 +1133,10 @@ function createNextAuthCurrentUserRoute(config, Response) {
|
|
|
1021
1133
|
}
|
|
1022
1134
|
return Response.json(user, { status: 200 });
|
|
1023
1135
|
} catch (error) {
|
|
1024
|
-
|
|
1025
|
-
|
|
1136
|
+
return Response.json(
|
|
1137
|
+
{ error: { message: "Internal Server Error", code: "GET_USER_ERROR" } },
|
|
1138
|
+
{ status: 500 }
|
|
1139
|
+
);
|
|
1026
1140
|
}
|
|
1027
1141
|
};
|
|
1028
1142
|
}
|