@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.cjs
CHANGED
|
@@ -49,10 +49,21 @@ module.exports = __toCommonJS(next_exports);
|
|
|
49
49
|
|
|
50
50
|
// src/media/resolve.ts
|
|
51
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 = [
|
|
52
|
+
var MEDIA_FIELD_SUFFIXES = [
|
|
53
|
+
"_image",
|
|
54
|
+
"_media",
|
|
55
|
+
"_photo",
|
|
56
|
+
"_thumbnail",
|
|
57
|
+
"_avatar",
|
|
58
|
+
"_icon",
|
|
59
|
+
"_file",
|
|
60
|
+
"_document",
|
|
61
|
+
"_attachment",
|
|
62
|
+
"_download"
|
|
63
|
+
];
|
|
53
64
|
function isMediaField(fieldName) {
|
|
54
65
|
const lower = fieldName.toLowerCase();
|
|
55
|
-
if (lower === "image" || lower === "media" || lower === "photo" || lower === "thumbnail" || lower === "avatar" || lower === "icon") {
|
|
66
|
+
if (lower === "image" || lower === "media" || lower === "photo" || lower === "thumbnail" || lower === "avatar" || lower === "icon" || lower === "file" || lower === "document" || lower === "attachment" || lower === "download") {
|
|
56
67
|
return true;
|
|
57
68
|
}
|
|
58
69
|
return MEDIA_FIELD_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
@@ -123,6 +134,7 @@ async function handleGetPageBySlug(adapter, mediaAdapter, slug) {
|
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
// src/media/types.ts
|
|
137
|
+
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
126
138
|
var ALLOWED_MIME_TYPES = [
|
|
127
139
|
"image/jpeg",
|
|
128
140
|
"image/png",
|
|
@@ -130,6 +142,23 @@ var ALLOWED_MIME_TYPES = [
|
|
|
130
142
|
"image/webp",
|
|
131
143
|
"image/svg+xml"
|
|
132
144
|
];
|
|
145
|
+
var ALLOWED_DOCUMENT_MIME_TYPES = [
|
|
146
|
+
"application/pdf",
|
|
147
|
+
"application/msword",
|
|
148
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
149
|
+
"application/vnd.ms-excel",
|
|
150
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
151
|
+
"application/vnd.ms-powerpoint",
|
|
152
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
153
|
+
"text/plain",
|
|
154
|
+
"text/csv",
|
|
155
|
+
"application/zip",
|
|
156
|
+
"application/gzip"
|
|
157
|
+
];
|
|
158
|
+
var ALL_ALLOWED_MIME_TYPES = [
|
|
159
|
+
...ALLOWED_MIME_TYPES,
|
|
160
|
+
...ALLOWED_DOCUMENT_MIME_TYPES
|
|
161
|
+
];
|
|
133
162
|
|
|
134
163
|
// src/media/handlers.ts
|
|
135
164
|
var MediaValidationError = class extends Error {
|
|
@@ -140,16 +169,25 @@ var MediaValidationError = class extends Error {
|
|
|
140
169
|
}
|
|
141
170
|
};
|
|
142
171
|
function validateMimeType(mimeType) {
|
|
143
|
-
const allowed =
|
|
172
|
+
const allowed = ALL_ALLOWED_MIME_TYPES;
|
|
144
173
|
if (!allowed.includes(mimeType)) {
|
|
145
174
|
throw new MediaValidationError(
|
|
146
|
-
`Invalid file type: ${mimeType}. Allowed types: ${
|
|
175
|
+
`Invalid file type: ${mimeType}. Allowed types: ${ALL_ALLOWED_MIME_TYPES.join(", ")}`,
|
|
147
176
|
"INVALID_MIME_TYPE"
|
|
148
177
|
);
|
|
149
178
|
}
|
|
150
179
|
}
|
|
180
|
+
function validateFileSize(size) {
|
|
181
|
+
if (size > MAX_FILE_SIZE) {
|
|
182
|
+
throw new MediaValidationError(
|
|
183
|
+
`File size exceeds maximum allowed size of ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
|
184
|
+
"FILE_TOO_LARGE"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
151
188
|
async function handleUploadMedia(adapter, input) {
|
|
152
189
|
validateMimeType(input.mimeType);
|
|
190
|
+
validateFileSize(input.size);
|
|
153
191
|
return adapter.upload(input);
|
|
154
192
|
}
|
|
155
193
|
async function handleGetMedia(adapter, id) {
|
|
@@ -196,6 +234,12 @@ var SANITIZE_OPTIONS = {
|
|
|
196
234
|
function sanitizeString(value) {
|
|
197
235
|
return (0, import_sanitize_html.default)(value, SANITIZE_OPTIONS);
|
|
198
236
|
}
|
|
237
|
+
function stripTags(value) {
|
|
238
|
+
return (0, import_sanitize_html.default)(value, {
|
|
239
|
+
allowedTags: [],
|
|
240
|
+
allowedAttributes: {}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
199
243
|
function sanitizeValue(value) {
|
|
200
244
|
if (typeof value === "string") {
|
|
201
245
|
return sanitizeString(value);
|
|
@@ -262,10 +306,11 @@ var StorageValidationError = class extends Error {
|
|
|
262
306
|
}
|
|
263
307
|
};
|
|
264
308
|
async function handleCreatePage(adapter, input) {
|
|
265
|
-
|
|
309
|
+
const sanitizedTitle = stripTags(input.title).trim();
|
|
310
|
+
if (!sanitizedTitle) {
|
|
266
311
|
throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
|
|
267
312
|
}
|
|
268
|
-
const slug = input.slug?.trim() || generateSlug(
|
|
313
|
+
const slug = input.slug?.trim() || generateSlug(sanitizedTitle);
|
|
269
314
|
if (!slug) {
|
|
270
315
|
throw new StorageValidationError(
|
|
271
316
|
"Could not generate a valid slug from the provided title",
|
|
@@ -278,6 +323,7 @@ async function handleCreatePage(adapter, input) {
|
|
|
278
323
|
const sanitizedSections = input.sections ? sanitizeSectionData(input.sections) : void 0;
|
|
279
324
|
return adapter.createPage({
|
|
280
325
|
...input,
|
|
326
|
+
title: sanitizedTitle,
|
|
281
327
|
slug: uniqueSlug,
|
|
282
328
|
sections: sanitizedSections
|
|
283
329
|
});
|
|
@@ -286,8 +332,12 @@ async function handleUpdatePage(adapter, input) {
|
|
|
286
332
|
if (!input.id.trim()) {
|
|
287
333
|
throw new StorageValidationError("Page ID must not be empty", "EMPTY_ID");
|
|
288
334
|
}
|
|
289
|
-
|
|
290
|
-
|
|
335
|
+
let sanitizedTitle;
|
|
336
|
+
if (input.title !== void 0) {
|
|
337
|
+
sanitizedTitle = stripTags(input.title).trim();
|
|
338
|
+
if (!sanitizedTitle) {
|
|
339
|
+
throw new StorageValidationError("Page title must not be empty", "EMPTY_TITLE");
|
|
340
|
+
}
|
|
291
341
|
}
|
|
292
342
|
if (input.slug !== void 0) {
|
|
293
343
|
const slug = input.slug.trim();
|
|
@@ -300,7 +350,11 @@ async function handleUpdatePage(adapter, input) {
|
|
|
300
350
|
throw new StorageValidationError(`Slug "${slug}" is already in use`, "DUPLICATE_SLUG");
|
|
301
351
|
}
|
|
302
352
|
}
|
|
303
|
-
const sanitizedInput =
|
|
353
|
+
const sanitizedInput = {
|
|
354
|
+
...input,
|
|
355
|
+
...sanitizedTitle !== void 0 && { title: sanitizedTitle },
|
|
356
|
+
...input.sections && { sections: sanitizeSectionData(input.sections) }
|
|
357
|
+
};
|
|
304
358
|
return adapter.updatePage(sanitizedInput);
|
|
305
359
|
}
|
|
306
360
|
async function handleDeletePage(adapter, id) {
|
|
@@ -310,38 +364,46 @@ async function handleDeletePage(adapter, id) {
|
|
|
310
364
|
return adapter.deletePage(id);
|
|
311
365
|
}
|
|
312
366
|
async function handleCreateNavigation(adapter, input) {
|
|
313
|
-
|
|
367
|
+
const sanitizedName = stripTags(input.name).trim();
|
|
368
|
+
if (!sanitizedName) {
|
|
314
369
|
throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
|
|
315
370
|
}
|
|
316
371
|
const existingNavigations = await adapter.listNavigations();
|
|
317
372
|
const existingNames = existingNavigations.map((n) => n.name);
|
|
318
|
-
if (existingNames.includes(
|
|
373
|
+
if (existingNames.includes(sanitizedName)) {
|
|
319
374
|
throw new StorageValidationError(
|
|
320
|
-
`Navigation name "${
|
|
375
|
+
`Navigation name "${sanitizedName}" is already in use`,
|
|
321
376
|
"DUPLICATE_NAME"
|
|
322
377
|
);
|
|
323
378
|
}
|
|
324
|
-
return adapter.createNavigation(
|
|
379
|
+
return adapter.createNavigation({
|
|
380
|
+
...input,
|
|
381
|
+
name: sanitizedName
|
|
382
|
+
});
|
|
325
383
|
}
|
|
326
384
|
async function handleUpdateNavigation(adapter, input) {
|
|
327
385
|
if (!input.id.trim()) {
|
|
328
386
|
throw new StorageValidationError("Navigation ID must not be empty", "EMPTY_ID");
|
|
329
387
|
}
|
|
388
|
+
let sanitizedName;
|
|
330
389
|
if (input.name !== void 0) {
|
|
331
|
-
|
|
332
|
-
if (!
|
|
390
|
+
sanitizedName = stripTags(input.name).trim();
|
|
391
|
+
if (!sanitizedName) {
|
|
333
392
|
throw new StorageValidationError("Navigation name must not be empty", "EMPTY_NAME");
|
|
334
393
|
}
|
|
335
394
|
const existingNavigations = await adapter.listNavigations();
|
|
336
395
|
const existingNames = existingNavigations.filter((n) => n.id !== input.id).map((n) => n.name);
|
|
337
|
-
if (existingNames.includes(
|
|
396
|
+
if (existingNames.includes(sanitizedName)) {
|
|
338
397
|
throw new StorageValidationError(
|
|
339
|
-
`Navigation name "${
|
|
398
|
+
`Navigation name "${sanitizedName}" is already in use`,
|
|
340
399
|
"DUPLICATE_NAME"
|
|
341
400
|
);
|
|
342
401
|
}
|
|
343
402
|
}
|
|
344
|
-
return adapter.updateNavigation(
|
|
403
|
+
return adapter.updateNavigation({
|
|
404
|
+
...input,
|
|
405
|
+
...sanitizedName !== void 0 && { name: sanitizedName }
|
|
406
|
+
});
|
|
345
407
|
}
|
|
346
408
|
async function handleDeleteNavigation(adapter, id) {
|
|
347
409
|
if (!id.trim()) {
|
|
@@ -377,7 +439,7 @@ function errorResponse(error, fallbackStatus = 500) {
|
|
|
377
439
|
if (error instanceof StorageValidationError || error instanceof MediaValidationError || error instanceof SyntaxError) {
|
|
378
440
|
return jsonResponse({ error: getErrorMessage(error) }, 400);
|
|
379
441
|
}
|
|
380
|
-
return jsonResponse({ error:
|
|
442
|
+
return jsonResponse({ error: "Internal Server Error" }, fallbackStatus);
|
|
381
443
|
}
|
|
382
444
|
async function resolveParams(context) {
|
|
383
445
|
return context.params;
|
|
@@ -701,11 +763,51 @@ function createNextPageByIdRoute(config) {
|
|
|
701
763
|
}
|
|
702
764
|
};
|
|
703
765
|
}
|
|
766
|
+
function getQueryParam(url, name) {
|
|
767
|
+
const queryStart = url.indexOf("?");
|
|
768
|
+
if (queryStart === -1) return null;
|
|
769
|
+
for (const pair of url.slice(queryStart + 1).split("&")) {
|
|
770
|
+
const eqIndex = pair.indexOf("=");
|
|
771
|
+
const key = decodeURIComponent(eqIndex === -1 ? pair : pair.slice(0, eqIndex));
|
|
772
|
+
if (key === name) return eqIndex === -1 ? "" : decodeURIComponent(pair.slice(eqIndex + 1));
|
|
773
|
+
}
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
function parseMediaFilter(request) {
|
|
777
|
+
if (!request.url) {
|
|
778
|
+
return void 0;
|
|
779
|
+
}
|
|
780
|
+
const category = getQueryParam(request.url, "category");
|
|
781
|
+
const limit = getQueryParam(request.url, "limit");
|
|
782
|
+
const offset = getQueryParam(request.url, "offset");
|
|
783
|
+
const filter = {};
|
|
784
|
+
let hasFilter = false;
|
|
785
|
+
if (category === "image" || category === "document") {
|
|
786
|
+
filter.category = category;
|
|
787
|
+
hasFilter = true;
|
|
788
|
+
}
|
|
789
|
+
if (limit) {
|
|
790
|
+
const parsed = Number.parseInt(limit, 10);
|
|
791
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
792
|
+
filter.limit = parsed;
|
|
793
|
+
hasFilter = true;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (offset) {
|
|
797
|
+
const parsed = Number.parseInt(offset, 10);
|
|
798
|
+
if (!Number.isNaN(parsed) && parsed >= 0) {
|
|
799
|
+
filter.offset = parsed;
|
|
800
|
+
hasFilter = true;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return hasFilter ? filter : void 0;
|
|
804
|
+
}
|
|
704
805
|
function createNextMediaRoute(config) {
|
|
705
806
|
return {
|
|
706
|
-
GET: async (
|
|
807
|
+
GET: async (request) => {
|
|
707
808
|
try {
|
|
708
|
-
const
|
|
809
|
+
const filter = parseMediaFilter(request);
|
|
810
|
+
const media = await handleListMedia(config.mediaAdapter, filter);
|
|
709
811
|
return jsonResponse(media);
|
|
710
812
|
} catch (error) {
|
|
711
813
|
return errorResponse(error);
|
|
@@ -966,8 +1068,10 @@ function createNextAuthOAuthRoute(config, Response) {
|
|
|
966
1068
|
{ status: 400 }
|
|
967
1069
|
);
|
|
968
1070
|
}
|
|
969
|
-
|
|
970
|
-
|
|
1071
|
+
return Response.json(
|
|
1072
|
+
{ error: { message: "Internal Server Error", code: "OAUTH_ERROR" } },
|
|
1073
|
+
{ status: 500 }
|
|
1074
|
+
);
|
|
971
1075
|
}
|
|
972
1076
|
};
|
|
973
1077
|
}
|
|
@@ -985,8 +1089,10 @@ function createNextAuthSignInRoute(config, Response) {
|
|
|
985
1089
|
{ status: 400 }
|
|
986
1090
|
);
|
|
987
1091
|
}
|
|
988
|
-
|
|
989
|
-
|
|
1092
|
+
return Response.json(
|
|
1093
|
+
{ error: { message: "Invalid credentials", code: "AUTH_ERROR" } },
|
|
1094
|
+
{ status: 401 }
|
|
1095
|
+
);
|
|
990
1096
|
}
|
|
991
1097
|
};
|
|
992
1098
|
}
|
|
@@ -1003,8 +1109,10 @@ function createNextAuthSignOutRoute(config, Response) {
|
|
|
1003
1109
|
await handleSignOut(config.authAdapter, token);
|
|
1004
1110
|
return Response.json({ message: "Signed out successfully" }, { status: 200 });
|
|
1005
1111
|
} catch (error) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1112
|
+
return Response.json(
|
|
1113
|
+
{ error: { message: "Internal Server Error", code: "SIGNOUT_ERROR" } },
|
|
1114
|
+
{ status: 500 }
|
|
1115
|
+
);
|
|
1008
1116
|
}
|
|
1009
1117
|
};
|
|
1010
1118
|
}
|
|
@@ -1027,8 +1135,10 @@ function createNextAuthVerifyRoute(config, Response) {
|
|
|
1027
1135
|
}
|
|
1028
1136
|
return Response.json(user, { status: 200 });
|
|
1029
1137
|
} catch (error) {
|
|
1030
|
-
|
|
1031
|
-
|
|
1138
|
+
return Response.json(
|
|
1139
|
+
{ error: { message: "Authentication failed", code: "VERIFY_ERROR" } },
|
|
1140
|
+
{ status: 401 }
|
|
1141
|
+
);
|
|
1032
1142
|
}
|
|
1033
1143
|
};
|
|
1034
1144
|
}
|
|
@@ -1046,8 +1156,10 @@ function createNextAuthRefreshRoute(config, Response) {
|
|
|
1046
1156
|
const session = await handleRefreshSession(config.authAdapter, refreshToken);
|
|
1047
1157
|
return Response.json(session, { status: 200 });
|
|
1048
1158
|
} catch (error) {
|
|
1049
|
-
|
|
1050
|
-
|
|
1159
|
+
return Response.json(
|
|
1160
|
+
{ error: { message: "Session refresh failed", code: "REFRESH_ERROR" } },
|
|
1161
|
+
{ status: 401 }
|
|
1162
|
+
);
|
|
1051
1163
|
}
|
|
1052
1164
|
};
|
|
1053
1165
|
}
|
|
@@ -1070,8 +1182,10 @@ function createNextAuthCurrentUserRoute(config, Response) {
|
|
|
1070
1182
|
}
|
|
1071
1183
|
return Response.json(user, { status: 200 });
|
|
1072
1184
|
} catch (error) {
|
|
1073
|
-
|
|
1074
|
-
|
|
1185
|
+
return Response.json(
|
|
1186
|
+
{ error: { message: "Internal Server Error", code: "GET_USER_ERROR" } },
|
|
1187
|
+
{ status: 500 }
|
|
1188
|
+
);
|
|
1075
1189
|
}
|
|
1076
1190
|
};
|
|
1077
1191
|
}
|