@sonicjs-cms/core 3.0.0-beta.24 → 3.0.0-beta.25
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/bin/db-reset.js +39 -22
- package/dist/admin-documents-form.template-JWV5GITD.js +6 -0
- package/dist/{admin-documents-form.template-55V2ZGWT.js.map → admin-documents-form.template-JWV5GITD.js.map} +1 -1
- package/dist/admin-documents-form.template-UWKGVAMW.cjs +19 -0
- package/dist/{admin-layout-catalyst.template-6UQGLCKI.js.map → admin-documents-form.template-UWKGVAMW.cjs.map} +1 -1
- package/dist/admin-layout-catalyst.template-MOQ3BBXA.cjs +21 -0
- package/dist/{admin-layout-catalyst.template-3XZXDGF7.cjs.map → admin-layout-catalyst.template-MOQ3BBXA.cjs.map} +1 -1
- package/dist/admin-layout-catalyst.template-WCYMQVTZ.js +7 -0
- package/dist/{admin-documents-form.template-YNAVMJCV.cjs.map → admin-layout-catalyst.template-WCYMQVTZ.js.map} +1 -1
- package/dist/{chunk-RJ2O5IIE.js → chunk-2D3A4X52.js} +2 -2
- package/dist/{chunk-RJ2O5IIE.js.map → chunk-2D3A4X52.js.map} +1 -1
- package/dist/{chunk-FHPEM77Y.js → chunk-2OXFMMA3.js} +11 -10
- package/dist/chunk-2OXFMMA3.js.map +1 -0
- package/dist/{chunk-SOZUG52O.js → chunk-3KGY4IBW.js} +3 -3
- package/dist/{chunk-SOZUG52O.js.map → chunk-3KGY4IBW.js.map} +1 -1
- package/dist/{chunk-I767LVN7.cjs → chunk-6OA77UM7.cjs} +757 -471
- package/dist/chunk-6OA77UM7.cjs.map +1 -0
- package/dist/{chunk-URIBTX27.js → chunk-73RTFFMX.js} +3 -3
- package/dist/{chunk-URIBTX27.js.map → chunk-73RTFFMX.js.map} +1 -1
- package/dist/{chunk-VC5XVPLM.js → chunk-7P5OCUPS.js} +424 -138
- package/dist/chunk-7P5OCUPS.js.map +1 -0
- package/dist/{chunk-3TMTYJAJ.js → chunk-BDQ55MWV.js} +18 -2
- package/dist/chunk-BDQ55MWV.js.map +1 -0
- package/dist/{chunk-KKNEYIZJ.cjs → chunk-BJ6NJL2E.cjs} +7 -12
- package/dist/chunk-BJ6NJL2E.cjs.map +1 -0
- package/dist/{chunk-VGEZNFWH.js → chunk-C54MH5UI.js} +5 -4
- package/dist/chunk-C54MH5UI.js.map +1 -0
- package/dist/{chunk-BBEKHLTW.cjs → chunk-CN3T3TDI.cjs} +9 -9
- package/dist/{chunk-BBEKHLTW.cjs.map → chunk-CN3T3TDI.cjs.map} +1 -1
- package/dist/{chunk-LNI3FYSI.js → chunk-CWIGQYRR.js} +7 -12
- package/dist/chunk-CWIGQYRR.js.map +1 -0
- package/dist/{chunk-BP52BFIV.cjs → chunk-D6FWEYQE.cjs} +6 -5
- package/dist/chunk-D6FWEYQE.cjs.map +1 -0
- package/dist/{chunk-UIHIXTIS.js → chunk-D7RLBBHE.js} +2 -2
- package/dist/{chunk-UIHIXTIS.js.map → chunk-D7RLBBHE.js.map} +1 -1
- package/dist/{chunk-IXUHXTHW.cjs → chunk-DLTT7QZU.cjs} +17 -6
- package/dist/chunk-DLTT7QZU.cjs.map +1 -0
- package/dist/{chunk-BHND5PEV.js → chunk-E3TQG7AA.js} +3 -3
- package/dist/{chunk-BHND5PEV.js.map → chunk-E3TQG7AA.js.map} +1 -1
- package/dist/{chunk-37V3RET3.cjs → chunk-E7E3NL3Y.cjs} +24 -23
- package/dist/chunk-E7E3NL3Y.cjs.map +1 -0
- package/dist/{chunk-VYQVDL2M.js → chunk-F26HP3B6.js} +36 -5
- package/dist/chunk-F26HP3B6.js.map +1 -0
- package/dist/{chunk-QZGABF2M.js → chunk-FVAK4C6L.js} +17 -6
- package/dist/chunk-FVAK4C6L.js.map +1 -0
- package/dist/{chunk-BJROXF4N.js → chunk-HSDJDWLV.js} +19 -4
- package/dist/chunk-HSDJDWLV.js.map +1 -0
- package/dist/{chunk-QJUJNTIU.cjs → chunk-I7MIRRFJ.cjs} +43 -12
- package/dist/chunk-I7MIRRFJ.cjs.map +1 -0
- package/dist/{chunk-LTZ5WYOK.cjs → chunk-JCBDXEY2.cjs} +18 -2
- package/dist/chunk-JCBDXEY2.cjs.map +1 -0
- package/dist/{chunk-UYJQDDEE.cjs → chunk-MI2YXCK4.cjs} +14 -8
- package/dist/chunk-MI2YXCK4.cjs.map +1 -0
- package/dist/{chunk-A2SN3RVL.cjs → chunk-MOGK3U4A.cjs} +2 -2
- package/dist/{chunk-A2SN3RVL.cjs.map → chunk-MOGK3U4A.cjs.map} +1 -1
- package/dist/{chunk-E66UVONV.cjs → chunk-OUBUS74I.cjs} +24 -9
- package/dist/chunk-OUBUS74I.cjs.map +1 -0
- package/dist/{chunk-GMMCCIQO.cjs → chunk-RSBZITK3.cjs} +3 -3
- package/dist/{chunk-GMMCCIQO.cjs.map → chunk-RSBZITK3.cjs.map} +1 -1
- package/dist/{chunk-THFZSDIU.js → chunk-SXC4HPK6.js} +14 -8
- package/dist/chunk-SXC4HPK6.js.map +1 -0
- package/dist/{chunk-JMJM7DMM.cjs → chunk-TT3TQSCZ.cjs} +5 -5
- package/dist/{chunk-JMJM7DMM.cjs.map → chunk-TT3TQSCZ.cjs.map} +1 -1
- package/dist/{chunk-QCJHCSOK.cjs → chunk-YAURVGUK.cjs} +2 -2
- package/dist/{chunk-QCJHCSOK.cjs.map → chunk-YAURVGUK.cjs.map} +1 -1
- package/dist/config-4TIHSNW6.js +6 -0
- package/dist/{config-6FB2BXMP.js.map → config-4TIHSNW6.js.map} +1 -1
- package/dist/{config-OCH7S4WK.cjs → config-XN2ETAJ3.cjs} +5 -5
- package/dist/{config-OCH7S4WK.cjs.map → config-XN2ETAJ3.cjs.map} +1 -1
- package/dist/{define-plugin-D_6D9vVZ.d.ts → define-plugin-DDe8a_bh.d.ts} +3 -0
- package/dist/{define-plugin-DMcvEy_C.d.cts → define-plugin-rjXmGOgu.d.cts} +3 -0
- package/dist/index.cjs +408 -370
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +67 -29
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +35 -35
- package/dist/middleware.d.cts +4 -1
- package/dist/middleware.d.ts +4 -1
- package/dist/middleware.js +6 -6
- package/dist/migrations-JAHMI5MK.js +4 -0
- package/dist/{migrations-Z6D27NAW.js.map → migrations-JAHMI5MK.js.map} +1 -1
- package/dist/migrations-KU4V6T73.cjs +13 -0
- package/dist/{migrations-FGXZPKDC.cjs.map → migrations-KU4V6T73.cjs.map} +1 -1
- package/dist/plugins.cjs +40 -40
- package/dist/plugins.d.cts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +3 -3
- package/dist/rbac-AORZ6O3N.cjs +14 -0
- package/dist/{rbac-7QZ4JZ2E.cjs.map → rbac-AORZ6O3N.cjs.map} +1 -1
- package/dist/rbac-P44772FP.js +5 -0
- package/dist/{rbac-Y43IEQOP.js.map → rbac-P44772FP.js.map} +1 -1
- package/dist/routes.cjs +31 -31
- package/dist/routes.js +11 -11
- package/dist/services.cjs +18 -18
- package/dist/services.d.cts +1 -1
- package/dist/services.d.ts +1 -1
- package/dist/services.js +2 -2
- package/dist/{telemetry-Cku1ax74.d.cts → telemetry-QFlTv9f8.d.cts} +1 -1
- package/dist/{telemetry-Cku1ax74.d.ts → telemetry-QFlTv9f8.d.ts} +1 -1
- package/dist/templates.cjs +17 -17
- package/dist/templates.js +3 -3
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils.cjs +12 -12
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +2 -2
- package/package.json +5 -10
- package/dist/admin-documents-form.template-55V2ZGWT.js +0 -6
- package/dist/admin-documents-form.template-YNAVMJCV.cjs +0 -19
- package/dist/admin-layout-catalyst.template-3XZXDGF7.cjs +0 -21
- package/dist/admin-layout-catalyst.template-6UQGLCKI.js +0 -7
- package/dist/chunk-37V3RET3.cjs.map +0 -1
- package/dist/chunk-3TMTYJAJ.js.map +0 -1
- package/dist/chunk-BJROXF4N.js.map +0 -1
- package/dist/chunk-BP52BFIV.cjs.map +0 -1
- package/dist/chunk-E66UVONV.cjs.map +0 -1
- package/dist/chunk-FHPEM77Y.js.map +0 -1
- package/dist/chunk-I767LVN7.cjs.map +0 -1
- package/dist/chunk-IXUHXTHW.cjs.map +0 -1
- package/dist/chunk-KKNEYIZJ.cjs.map +0 -1
- package/dist/chunk-LNI3FYSI.js.map +0 -1
- package/dist/chunk-LTZ5WYOK.cjs.map +0 -1
- package/dist/chunk-QJUJNTIU.cjs.map +0 -1
- package/dist/chunk-QZGABF2M.js.map +0 -1
- package/dist/chunk-THFZSDIU.js.map +0 -1
- package/dist/chunk-UYJQDDEE.cjs.map +0 -1
- package/dist/chunk-VC5XVPLM.js.map +0 -1
- package/dist/chunk-VGEZNFWH.js.map +0 -1
- package/dist/chunk-VYQVDL2M.js.map +0 -1
- package/dist/config-6FB2BXMP.js +0 -6
- package/dist/migrations-FGXZPKDC.cjs +0 -13
- package/dist/migrations-Z6D27NAW.js +0 -4
- package/dist/rbac-7QZ4JZ2E.cjs +0 -14
- package/dist/rbac-Y43IEQOP.js +0 -5
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { isFirstUserRegistration, isRegistrationEnabled, authValidationService } from './chunk-VVYVFCML.js';
|
|
2
|
-
import { renderDocumentFormPage, renderAlert } from './chunk-
|
|
2
|
+
import { renderDocumentFormPage, renderAlert } from './chunk-E3TQG7AA.js';
|
|
3
3
|
import { getCacheService, CACHE_CONFIGS, SettingsService, getLogger, getAppInstance, buildRouteList, CATEGORY_INFO } from './chunk-2XAQTPAE.js';
|
|
4
|
-
import { DocumentTypeRegistry, requireAuth, requireRole, optionalAuth, isPluginActive, rateLimit, AuthManager, getJwtExpirySecondsFromDb, getJwtRefreshGraceSecondsFromDb, logActivity, generateCsrfToken } from './chunk-
|
|
5
|
-
import { DocumentsService, documentSecondsToMs, RbacService } from './chunk-
|
|
4
|
+
import { DocumentTypeRegistry, requireAuth, requireRole, optionalAuth, isPluginActive, rateLimit, AuthManager, getJwtExpirySecondsFromDb, getJwtRefreshGraceSecondsFromDb, logActivity, generateCsrfToken } from './chunk-2OXFMMA3.js';
|
|
5
|
+
import { DocumentsService, documentSecondsToMs, RbacService } from './chunk-BDQ55MWV.js';
|
|
6
6
|
import { nanoid } from './chunk-UWUP2VMT.js';
|
|
7
|
-
import { getCollectionRegistry, collectionRecordToRow, loadCollectionConfigs, getVisibleCollections, PluginService, PLUGIN_REGISTRY, findPluginByCodeName, isCodeCollectionInternal, isDbDocTypeInternal, invalidateTenantCache } from './chunk-
|
|
8
|
-
import { MigrationService } from './chunk-
|
|
9
|
-
import { renderCheckboxPage, renderAlert as renderAlert$1, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-
|
|
10
|
-
import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-
|
|
11
|
-
import { createServiceSingleton, definePlugin, getPluginDefinition, applySchemaDefaults, renderSchemaFields, parseFormDataToSettings, getPluginMenu } from './chunk-
|
|
7
|
+
import { getCollectionRegistry, collectionRecordToRow, loadCollectionConfigs, getVisibleCollections, PluginService, PLUGIN_REGISTRY, findPluginByCodeName, isCodeCollectionInternal, isDbDocTypeInternal, invalidateTenantCache } from './chunk-2D3A4X52.js';
|
|
8
|
+
import { MigrationService } from './chunk-D7RLBBHE.js';
|
|
9
|
+
import { renderCheckboxPage, renderAlert as renderAlert$1, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-HSDJDWLV.js';
|
|
10
|
+
import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-SXC4HPK6.js';
|
|
11
|
+
import { createServiceSingleton, definePlugin, getPluginDefinition, applySchemaDefaults, renderSchemaFields, parseFormDataToSettings, getPluginMenu } from './chunk-C54MH5UI.js';
|
|
12
12
|
import { hasHookSystem, getTypedHooks } from './chunk-RNZFGN4R.js';
|
|
13
|
-
import { QueryFilterBuilder, getBlocksFieldConfig, parseBlocksValue } from './chunk-
|
|
13
|
+
import { QueryFilterBuilder, getBlocksFieldConfig, parseBlocksValue } from './chunk-FVAK4C6L.js';
|
|
14
14
|
import { metricsTracker } from './chunk-FICTAGD4.js';
|
|
15
|
-
import { getCoreVersion } from './chunk-
|
|
15
|
+
import { getCoreVersion } from './chunk-CWIGQYRR.js';
|
|
16
16
|
import { escapeHtml, sanitizeRichText, sanitizeInput } from './chunk-TQABQWOP.js';
|
|
17
17
|
import { Hono } from 'hono';
|
|
18
18
|
import { z } from 'zod';
|
|
@@ -45,6 +45,7 @@ var DocumentPermissionsService = class {
|
|
|
45
45
|
const publicGrants = baseGrants["public"] ?? [];
|
|
46
46
|
if (publicGrants.includes(permission)) return true;
|
|
47
47
|
} else if (principal.type === "role") {
|
|
48
|
+
if (principal.id === "public") continue;
|
|
48
49
|
const roleGrants = baseGrants[principal.id] ?? [];
|
|
49
50
|
if (roleGrants.includes(permission)) return true;
|
|
50
51
|
}
|
|
@@ -994,6 +995,32 @@ apiContentCrudRoutes.get("/:id", optionalAuth(), async (c) => {
|
|
|
994
995
|
{ collection: docRow.type_id, id: docRow.root_id, data: transformedContent.data },
|
|
995
996
|
"fire-and-forget"
|
|
996
997
|
);
|
|
998
|
+
const fieldsParam = c.req.query("fields");
|
|
999
|
+
if (fieldsParam) {
|
|
1000
|
+
const projFields = fieldsParam.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1001
|
+
if (projFields.length > 0) {
|
|
1002
|
+
const result = {};
|
|
1003
|
+
const dataSubFields = [];
|
|
1004
|
+
for (const f of projFields) {
|
|
1005
|
+
if (f === "data") {
|
|
1006
|
+
result.data = transformedContent.data;
|
|
1007
|
+
} else if (f.startsWith("data.")) {
|
|
1008
|
+
dataSubFields.push(f.slice(5));
|
|
1009
|
+
} else if (Object.prototype.hasOwnProperty.call(transformedContent, f)) {
|
|
1010
|
+
result[f] = transformedContent[f];
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (dataSubFields.length > 0 && !result.data) {
|
|
1014
|
+
result.data = {};
|
|
1015
|
+
for (const sub of dataSubFields) {
|
|
1016
|
+
if (transformedContent.data && Object.prototype.hasOwnProperty.call(transformedContent.data, sub)) {
|
|
1017
|
+
result.data[sub] = transformedContent.data[sub];
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return c.json({ data: result });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
997
1024
|
return c.json({ data: transformedContent });
|
|
998
1025
|
} catch (error) {
|
|
999
1026
|
console.error("Error fetching content:", error);
|
|
@@ -1057,8 +1084,8 @@ apiContentCrudRoutes.post("/", requireAuth(), requireRole(["admin", "editor", "a
|
|
|
1057
1084
|
user?.userId
|
|
1058
1085
|
);
|
|
1059
1086
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1060
|
-
await cache.invalidate("content-filtered:*");
|
|
1061
|
-
await cache.invalidate(
|
|
1087
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1088
|
+
await cache.invalidate(`api:collection-content-filtered:${backing.coll.name}:*`);
|
|
1062
1089
|
dispatchHookEvent(
|
|
1063
1090
|
c,
|
|
1064
1091
|
"content:after:create",
|
|
@@ -1121,8 +1148,8 @@ apiContentCrudRoutes.put("/:id", requireAuth(), requireRole(["admin", "editor",
|
|
|
1121
1148
|
await svc.unpublish(pub.id);
|
|
1122
1149
|
}
|
|
1123
1150
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1124
|
-
await cache.invalidate("content-filtered:*");
|
|
1125
|
-
await cache.invalidate(
|
|
1151
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1152
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
1126
1153
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
1127
1154
|
const saved = await db.prepare("SELECT * FROM documents WHERE id = ?").bind(newDraft.id).first();
|
|
1128
1155
|
const savedData = saved?.data ? JSON.parse(saved.data) : {};
|
|
@@ -1161,8 +1188,8 @@ apiContentCrudRoutes.delete("/:id", requireAuth(), requireRole(["admin", "editor
|
|
|
1161
1188
|
const now = Math.floor(Date.now() / 1e3);
|
|
1162
1189
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = ?").bind(now, now, id, tenantId).run();
|
|
1163
1190
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1164
|
-
await cache.invalidate("content-filtered:*");
|
|
1165
|
-
await cache.invalidate(
|
|
1191
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1192
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
1166
1193
|
dispatchHookEvent(c, "content:after:delete", { collection: docRow.type_id, id, data: {}, user: actor }, "fire-and-forget");
|
|
1167
1194
|
return c.json({ success: true });
|
|
1168
1195
|
}
|
|
@@ -1178,6 +1205,22 @@ apiContentCrudRoutes.delete("/:id", requireAuth(), requireRole(["admin", "editor
|
|
|
1178
1205
|
var api_content_crud_default = apiContentCrudRoutes;
|
|
1179
1206
|
|
|
1180
1207
|
// src/routes/api.ts
|
|
1208
|
+
function isAnonPrincipal(principalSet) {
|
|
1209
|
+
return principalSet.length === 1 && principalSet[0]?.type === "public";
|
|
1210
|
+
}
|
|
1211
|
+
async function typeReadAllowed(db, principalSet, docType, typeName) {
|
|
1212
|
+
const perms = new DocumentPermissionsService(db);
|
|
1213
|
+
if (perms.isAllowedSync(principalSet, [], "read", docType?.settings ?? {})) return true;
|
|
1214
|
+
const rbac = new RbacService(db);
|
|
1215
|
+
let anyRbacRoleFound = false;
|
|
1216
|
+
for (const p of principalSet) {
|
|
1217
|
+
if (p.type !== "role") continue;
|
|
1218
|
+
if (!await rbac.hasRbacRole(p.id)) continue;
|
|
1219
|
+
anyRbacRoleFound = true;
|
|
1220
|
+
if (await rbac.isGrantedForRole(p.id, `document_type:${typeName}`, "read")) return true;
|
|
1221
|
+
}
|
|
1222
|
+
return !anyRbacRoleFound;
|
|
1223
|
+
}
|
|
1181
1224
|
var DOC_SORTABLE_COLUMNS = /* @__PURE__ */ new Set([
|
|
1182
1225
|
"created_at",
|
|
1183
1226
|
"updated_at",
|
|
@@ -1261,6 +1304,33 @@ function mapDocRowToContent(row, collectionId) {
|
|
|
1261
1304
|
updated_at: documentSecondsToMs(row.updated_at)
|
|
1262
1305
|
};
|
|
1263
1306
|
}
|
|
1307
|
+
function projectFields(item, fields) {
|
|
1308
|
+
if (fields.length === 0) return item;
|
|
1309
|
+
const result = {};
|
|
1310
|
+
const dataSubFields = [];
|
|
1311
|
+
for (const f of fields) {
|
|
1312
|
+
if (f === "data") {
|
|
1313
|
+
result.data = item.data;
|
|
1314
|
+
} else if (f.startsWith("data.")) {
|
|
1315
|
+
dataSubFields.push(f.slice(5));
|
|
1316
|
+
} else if (Object.prototype.hasOwnProperty.call(item, f)) {
|
|
1317
|
+
result[f] = item[f];
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if (dataSubFields.length > 0 && !result.data) {
|
|
1321
|
+
result.data = {};
|
|
1322
|
+
for (const sub of dataSubFields) {
|
|
1323
|
+
if (item.data && Object.prototype.hasOwnProperty.call(item.data, sub)) {
|
|
1324
|
+
result.data[sub] = item.data[sub];
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return result;
|
|
1329
|
+
}
|
|
1330
|
+
function parseFieldsParam(raw2) {
|
|
1331
|
+
if (!raw2) return [];
|
|
1332
|
+
return raw2.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1333
|
+
}
|
|
1264
1334
|
var apiRoutes = new Hono();
|
|
1265
1335
|
apiRoutes.use("*", async (c, next) => {
|
|
1266
1336
|
const startTime = Date.now();
|
|
@@ -1750,7 +1820,12 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1750
1820
|
try {
|
|
1751
1821
|
const db = c.env.DB;
|
|
1752
1822
|
const queryParams = c.req.query();
|
|
1823
|
+
const projFields = parseFieldsParam(queryParams.fields);
|
|
1824
|
+
if (queryParams.fields) delete queryParams.fields;
|
|
1753
1825
|
const role = c.get("user")?.role;
|
|
1826
|
+
const { tenantId: cTenantId, principalSet: cPrincipalSet } = getDocumentRequestContext(c);
|
|
1827
|
+
const cAnon = isAnonPrincipal(cPrincipalSet);
|
|
1828
|
+
const cNeedsAcl = !cAnon && !canReadNonPublicContent(role);
|
|
1754
1829
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1755
1830
|
let typeId;
|
|
1756
1831
|
let typeIds;
|
|
@@ -1796,7 +1871,7 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1796
1871
|
details: queryResult.errors
|
|
1797
1872
|
}, 400);
|
|
1798
1873
|
}
|
|
1799
|
-
const cacheEnabled = c.get("cacheEnabled");
|
|
1874
|
+
const cacheEnabled = c.get("cacheEnabled") && cAnon;
|
|
1800
1875
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1801
1876
|
const cacheKey = cache.generateKey("content-filtered", JSON.stringify({ filter: normalizedFilter, query: queryResult.sql }));
|
|
1802
1877
|
if (cacheEnabled) {
|
|
@@ -1826,11 +1901,41 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1826
1901
|
const stmt = db.prepare(queryResult.sql);
|
|
1827
1902
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1828
1903
|
const { results } = await boundStmt.all();
|
|
1829
|
-
|
|
1904
|
+
let aclResults = results;
|
|
1905
|
+
if (cNeedsAcl) {
|
|
1906
|
+
const typeReg = new DocumentTypeRegistry(db);
|
|
1907
|
+
const repo = new DocumentRepository(db, cTenantId);
|
|
1908
|
+
const uniqueTypeIds = [...new Set(results.map((r) => r.type_id))];
|
|
1909
|
+
const typeSettingsMap = /* @__PURE__ */ new Map();
|
|
1910
|
+
const typeRbacOnlyMap = /* @__PURE__ */ new Map();
|
|
1911
|
+
const typeDeniedMap = /* @__PURE__ */ new Map();
|
|
1912
|
+
await Promise.all(uniqueTypeIds.map(async (tid) => {
|
|
1913
|
+
const dt = await typeReg.findById(tid);
|
|
1914
|
+
const settings = dt?.settings ?? {};
|
|
1915
|
+
typeSettingsMap.set(tid, settings);
|
|
1916
|
+
const allowed2 = await typeReadAllowed(db, cPrincipalSet, dt, tid);
|
|
1917
|
+
if (!allowed2) {
|
|
1918
|
+
typeDeniedMap.set(tid, true);
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
const perms = new DocumentPermissionsService(db);
|
|
1922
|
+
typeRbacOnlyMap.set(tid, !perms.isAllowedSync(cPrincipalSet, [], "read", settings));
|
|
1923
|
+
}));
|
|
1924
|
+
const allowed = await Promise.all(
|
|
1925
|
+
results.map((row) => {
|
|
1926
|
+
const tid = row.type_id;
|
|
1927
|
+
if (typeDeniedMap.get(tid)) return Promise.resolve(false);
|
|
1928
|
+
if (typeRbacOnlyMap.get(tid)) return Promise.resolve(true);
|
|
1929
|
+
return repo.isAllowed(cPrincipalSet, row.root_id, "read", typeSettingsMap.get(tid) ?? {});
|
|
1930
|
+
})
|
|
1931
|
+
);
|
|
1932
|
+
aclResults = results.filter((_, i) => allowed[i]);
|
|
1933
|
+
}
|
|
1934
|
+
const transformedResults = aclResults.map((row) => mapDocRowToContent(row, collIdByName.get(row.type_id) ?? null));
|
|
1830
1935
|
const responseData = {
|
|
1831
|
-
data: transformedResults,
|
|
1936
|
+
data: projFields.length ? transformedResults.map((item) => projectFields(item, projFields)) : transformedResults,
|
|
1832
1937
|
meta: addTimingMeta(c, {
|
|
1833
|
-
count:
|
|
1938
|
+
count: aclResults.length,
|
|
1834
1939
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1835
1940
|
// D44: echo the caller's filter with the access policy applied (status=published forced for
|
|
1836
1941
|
// anonymous callers — the visible enforcement proof), NOT the internal doc-augmented where-tree.
|
|
@@ -1841,7 +1946,7 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1841
1946
|
}
|
|
1842
1947
|
}, executionStart)
|
|
1843
1948
|
};
|
|
1844
|
-
if (cacheEnabled) {
|
|
1949
|
+
if (cacheEnabled && projFields.length === 0) {
|
|
1845
1950
|
await cache.set(cacheKey, responseData);
|
|
1846
1951
|
}
|
|
1847
1952
|
return c.json(responseData);
|
|
@@ -1866,6 +1971,16 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1866
1971
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1867
1972
|
collIdByName.set(collection, record.id);
|
|
1868
1973
|
const role = c.get("user")?.role;
|
|
1974
|
+
const { tenantId: ccTenantId, principalSet: ccPrincipalSet } = getDocumentRequestContext(c);
|
|
1975
|
+
const ccAnon = isAnonPrincipal(ccPrincipalSet);
|
|
1976
|
+
const ccNeedsAcl = !ccAnon && !canReadNonPublicContent(role);
|
|
1977
|
+
let ccDocType;
|
|
1978
|
+
if (ccNeedsAcl) {
|
|
1979
|
+
ccDocType = await new DocumentTypeRegistry(db).findById(record.name);
|
|
1980
|
+
if (!await typeReadAllowed(db, ccPrincipalSet, ccDocType, record.name)) {
|
|
1981
|
+
return c.json({ error: "Forbidden" }, 403);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1869
1984
|
const filter = QueryFilterBuilder.parseFromQuery(queryParams);
|
|
1870
1985
|
const normalizedFilter = augmentFilterForDocuments(filter, { typeId: collection, role });
|
|
1871
1986
|
if (!normalizedFilter.limit) {
|
|
@@ -1880,7 +1995,7 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1880
1995
|
details: queryResult.errors
|
|
1881
1996
|
}, 400);
|
|
1882
1997
|
}
|
|
1883
|
-
const cacheEnabled = c.get("cacheEnabled");
|
|
1998
|
+
const cacheEnabled = c.get("cacheEnabled") && ccAnon;
|
|
1884
1999
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1885
2000
|
const includeCollection = queryParams.include?.split(",").map((s) => s.trim()).includes("collection");
|
|
1886
2001
|
const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql, includeCollection })}`);
|
|
@@ -1911,12 +2026,24 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1911
2026
|
const stmt = db.prepare(queryResult.sql);
|
|
1912
2027
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1913
2028
|
const { results } = await boundStmt.all();
|
|
1914
|
-
|
|
2029
|
+
let ccAclResults = results;
|
|
2030
|
+
if (ccNeedsAcl) {
|
|
2031
|
+
const ccPerms = new DocumentPermissionsService(db);
|
|
2032
|
+
const ccBaseGrantsPass = ccPerms.isAllowedSync(ccPrincipalSet, [], "read", ccDocType?.settings ?? {});
|
|
2033
|
+
if (ccBaseGrantsPass) {
|
|
2034
|
+
const repo = new DocumentRepository(db, ccTenantId);
|
|
2035
|
+
const allowed = await Promise.all(
|
|
2036
|
+
results.map((row) => repo.isAllowed(ccPrincipalSet, row.root_id, "read", ccDocType?.settings ?? {}))
|
|
2037
|
+
);
|
|
2038
|
+
ccAclResults = results.filter((_, i) => allowed[i]);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
const transformedResults = ccAclResults.map((row) => mapDocRowToContent(row, collIdByName.get(row.type_id) ?? null));
|
|
1915
2042
|
const responseData = {
|
|
1916
2043
|
data: transformedResults,
|
|
1917
2044
|
meta: addTimingMeta(c, {
|
|
1918
2045
|
...includeCollection ? { collection: collectionRecordToRow(record) } : {},
|
|
1919
|
-
count:
|
|
2046
|
+
count: ccAclResults.length,
|
|
1920
2047
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1921
2048
|
// D44: echo the caller's filter with the access policy applied (status=published forced for
|
|
1922
2049
|
// anonymous callers — the visible enforcement proof), NOT the internal doc-augmented where-tree.
|
|
@@ -1946,6 +2073,8 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1946
2073
|
const collection = c.req.param("collection");
|
|
1947
2074
|
const db = c.env.DB;
|
|
1948
2075
|
const queryParams = c.req.query();
|
|
2076
|
+
const projFields = parseFieldsParam(queryParams.fields);
|
|
2077
|
+
if (queryParams.fields) delete queryParams.fields;
|
|
1949
2078
|
const record = getCollectionRegistry().getBySlugOrName(collection);
|
|
1950
2079
|
if (!record || record.isActive === false) {
|
|
1951
2080
|
return c.json({ error: "Collection not found" }, 404);
|
|
@@ -1953,6 +2082,16 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1953
2082
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1954
2083
|
collIdByName.set(record.name, record.id);
|
|
1955
2084
|
const role = c.get("user")?.role;
|
|
2085
|
+
const { tenantId, principalSet } = getDocumentRequestContext(c);
|
|
2086
|
+
const anon = isAnonPrincipal(principalSet);
|
|
2087
|
+
const needsAcl = !anon && !canReadNonPublicContent(role);
|
|
2088
|
+
let docTypeForCheck;
|
|
2089
|
+
if (needsAcl) {
|
|
2090
|
+
docTypeForCheck = await new DocumentTypeRegistry(db).findById(record.name);
|
|
2091
|
+
if (!await typeReadAllowed(db, principalSet, docTypeForCheck, record.name)) {
|
|
2092
|
+
return c.json({ error: "Forbidden" }, 403);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
1956
2095
|
const filter = QueryFilterBuilder.parseFromQuery(queryParams);
|
|
1957
2096
|
const normalizedFilter = augmentFilterForDocuments(filter, { typeId: record.name, role });
|
|
1958
2097
|
if (!normalizedFilter.limit) normalizedFilter.limit = 50;
|
|
@@ -1964,7 +2103,7 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1964
2103
|
}
|
|
1965
2104
|
const collectionCache = record.cache;
|
|
1966
2105
|
const collectionCacheDisabled = collectionCache?.enabled === false;
|
|
1967
|
-
const cacheEnabled = c.get("cacheEnabled") && !collectionCacheDisabled;
|
|
2106
|
+
const cacheEnabled = c.get("cacheEnabled") && !collectionCacheDisabled && projFields.length === 0 && anon;
|
|
1968
2107
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1969
2108
|
const includeCollection = queryParams.include?.split(",").map((s) => s.trim()).includes("collection");
|
|
1970
2109
|
const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql, includeCollection })}`);
|
|
@@ -1982,12 +2121,25 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1982
2121
|
const stmt = db.prepare(queryResult.sql);
|
|
1983
2122
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1984
2123
|
const { results } = await boundStmt.all();
|
|
1985
|
-
|
|
2124
|
+
let aclResults = results;
|
|
2125
|
+
if (needsAcl) {
|
|
2126
|
+
const perms = new DocumentPermissionsService(db);
|
|
2127
|
+
const baseGrantsPass = perms.isAllowedSync(principalSet, [], "read", docTypeForCheck?.settings ?? {});
|
|
2128
|
+
if (!baseGrantsPass) {
|
|
2129
|
+
} else {
|
|
2130
|
+
const repo = new DocumentRepository(db, tenantId);
|
|
2131
|
+
const allowed = await Promise.all(
|
|
2132
|
+
results.map((row) => repo.isAllowed(principalSet, row.root_id, "read", docTypeForCheck?.settings ?? {}))
|
|
2133
|
+
);
|
|
2134
|
+
aclResults = results.filter((_, i) => allowed[i]);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
const transformedResults = aclResults.map((row) => mapDocRowToContent(row, collIdByName.get(row.type_id) ?? null));
|
|
1986
2138
|
const responseData = {
|
|
1987
|
-
data: transformedResults,
|
|
2139
|
+
data: projFields.length ? transformedResults.map((item) => projectFields(item, projFields)) : transformedResults,
|
|
1988
2140
|
meta: addTimingMeta(c, {
|
|
1989
2141
|
...includeCollection ? { collection: collectionRecordToRow(record) } : {},
|
|
1990
|
-
count:
|
|
2142
|
+
count: aclResults.length,
|
|
1991
2143
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1992
2144
|
filter: normalizePublicContentFilter(filter, role),
|
|
1993
2145
|
cache: { hit: false, source: "database" }
|
|
@@ -2018,6 +2170,23 @@ apiRoutes.get("/:collection/:id", optionalAuth(), async (c) => {
|
|
|
2018
2170
|
privileged ? "SELECT * FROM documents WHERE root_id = ? AND type_id = ? AND tenant_id = 'default' AND is_current_draft = 1 AND deleted_at IS NULL" : "SELECT * FROM documents WHERE root_id = ? AND type_id = ? AND tenant_id = 'default' AND is_published = 1 AND deleted_at IS NULL"
|
|
2019
2171
|
).bind(id, record.name).first();
|
|
2020
2172
|
if (!docRow) return c.json({ error: "Content not found" }, 404);
|
|
2173
|
+
const colRole = c.get("user")?.role;
|
|
2174
|
+
const { tenantId: colTenantId, principalSet: colPrincipalSet } = getDocumentRequestContext(c);
|
|
2175
|
+
const colNeedsAcl = !isAnonPrincipal(colPrincipalSet) && !canReadNonPublicContent(colRole);
|
|
2176
|
+
if (colNeedsAcl) {
|
|
2177
|
+
const docType = await new DocumentTypeRegistry(db).findById(record.name);
|
|
2178
|
+
if (!await typeReadAllowed(db, colPrincipalSet, docType, record.name)) {
|
|
2179
|
+
return c.json({ error: "Content not found" }, 404);
|
|
2180
|
+
}
|
|
2181
|
+
const perms = new DocumentPermissionsService(db);
|
|
2182
|
+
if (perms.isAllowedSync(colPrincipalSet, [], "read", docType?.settings ?? {})) {
|
|
2183
|
+
const repo = new DocumentRepository(db, colTenantId);
|
|
2184
|
+
if (!await repo.isAllowed(colPrincipalSet, docRow.root_id, "read", docType?.settings ?? {})) {
|
|
2185
|
+
return c.json({ error: "Content not found" }, 404);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
const projFields = parseFieldsParam(c.req.query("fields"));
|
|
2021
2190
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
2022
2191
|
const transformedContent = {
|
|
2023
2192
|
id: docRow.root_id,
|
|
@@ -2030,7 +2199,7 @@ apiRoutes.get("/:collection/:id", optionalAuth(), async (c) => {
|
|
|
2030
2199
|
updated_at: documentSecondsToMs(docRow.updated_at)
|
|
2031
2200
|
};
|
|
2032
2201
|
dispatchHookEvent(c, "content:read", { collection: docRow.type_id, id: docRow.root_id, data: transformedContent.data }, "fire-and-forget");
|
|
2033
|
-
return c.json({ data: transformedContent });
|
|
2202
|
+
return c.json({ data: projFields.length ? projectFields(transformedContent, projFields) : transformedContent });
|
|
2034
2203
|
} catch (error) {
|
|
2035
2204
|
console.error("Error fetching content:", error);
|
|
2036
2205
|
return c.json({ error: "Failed to fetch content", details: error instanceof Error ? error.message : String(error) }, 500);
|
|
@@ -2060,8 +2229,8 @@ apiRoutes.post("/:collection", requireAuth(), requireRole(["admin", "editor", "a
|
|
|
2060
2229
|
const svc = new DocumentsService(db, { queryableFields: backing.docType.queryableFields ?? [], typeSchemaVersion: backing.docType.schemaVersion ?? 1, maxVersionsPerRoot: backing.docType.settings?.maxVersionsPerRoot ?? 50, tenantId: "default", versioning: backing.docType.settings?.versioning ?? false });
|
|
2061
2230
|
const doc = await svc.create(createDocumentSchema.parse({ typeId: backing.coll.name, tenantId: "default", locale: "default", title, slug: finalSlug, data: hookData, publishOnCreate: (status || "draft") === "published" }), user?.userId);
|
|
2062
2231
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2063
|
-
await cache.invalidate("content-filtered:*");
|
|
2064
|
-
await cache.invalidate(
|
|
2232
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2233
|
+
await cache.invalidate(`api:collection-content-filtered:${backing.coll.name}:*`);
|
|
2065
2234
|
dispatchHookEvent(c, "content:after:create", { collection: backing.coll.name, id: doc.rootId, data: doc.data ?? {}, user: actor }, "fire-and-forget");
|
|
2066
2235
|
return c.json({ data: { id: doc.rootId, title: doc.title, slug: doc.slug, status: doc.status, collectionId: backing.coll.id, data: doc.data, created_at: documentSecondsToMs(doc.createdAt), updated_at: documentSecondsToMs(doc.updatedAt) } }, 201);
|
|
2067
2236
|
} catch (error) {
|
|
@@ -2103,8 +2272,8 @@ apiRoutes.put("/:collection/:id", requireAuth(), requireRole(["admin", "editor",
|
|
|
2103
2272
|
await svc.unpublish(pub.id);
|
|
2104
2273
|
}
|
|
2105
2274
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2106
|
-
await cache.invalidate("content-filtered:*");
|
|
2107
|
-
await cache.invalidate(
|
|
2275
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2276
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
2108
2277
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
2109
2278
|
const saved = await db.prepare("SELECT * FROM documents WHERE id = ?").bind(newDraft.id).first();
|
|
2110
2279
|
const savedData = saved?.data ? JSON.parse(saved.data) : {};
|
|
@@ -2136,8 +2305,8 @@ apiRoutes.delete("/:collection/:id", requireAuth(), requireRole(["admin", "edito
|
|
|
2136
2305
|
const now = Math.floor(Date.now() / 1e3);
|
|
2137
2306
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = 'default'").bind(now, now, id).run();
|
|
2138
2307
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2139
|
-
await cache.invalidate("content-filtered:*");
|
|
2140
|
-
await cache.invalidate(
|
|
2308
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2309
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
2141
2310
|
dispatchHookEvent(c, "content:after:delete", { collection: docRow.type_id, id, data: {}, user: actor }, "fire-and-forget");
|
|
2142
2311
|
return c.json({ success: true });
|
|
2143
2312
|
} catch (error) {
|
|
@@ -2699,9 +2868,13 @@ apiMediaRoutes.post("/create-folder", async (c) => {
|
|
|
2699
2868
|
error: "Folder name can only contain lowercase letters, numbers, hyphens, and underscores"
|
|
2700
2869
|
}, 400);
|
|
2701
2870
|
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2871
|
+
let folderExists = false;
|
|
2872
|
+
try {
|
|
2873
|
+
const existing = await c.env.DB.prepare("SELECT COUNT(*) as count FROM documents WHERE type_id = 'media_asset' AND tenant_id = 'default' AND json_extract(data, '$.folder') = ? AND deleted_at IS NULL AND is_current_draft = 1").bind(folderName).first();
|
|
2874
|
+
folderExists = (existing?.count ?? 0) > 0;
|
|
2875
|
+
} catch {
|
|
2876
|
+
}
|
|
2877
|
+
if (folderExists) {
|
|
2705
2878
|
return c.json({
|
|
2706
2879
|
success: false,
|
|
2707
2880
|
error: `Folder "${folderName}" already exists`
|
|
@@ -3411,7 +3584,7 @@ adminApiRoutes.patch("/collections/:id", collectionsReadOnly);
|
|
|
3411
3584
|
adminApiRoutes.delete("/collections/:id", collectionsReadOnly);
|
|
3412
3585
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
3413
3586
|
try {
|
|
3414
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
3587
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-JAHMI5MK.js');
|
|
3415
3588
|
const db = c.env.DB;
|
|
3416
3589
|
const migrationService = new MigrationService2(db);
|
|
3417
3590
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -3454,7 +3627,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
3454
3627
|
});
|
|
3455
3628
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
3456
3629
|
try {
|
|
3457
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
3630
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-JAHMI5MK.js');
|
|
3458
3631
|
const db = c.env.DB;
|
|
3459
3632
|
const migrationService = new MigrationService2(db);
|
|
3460
3633
|
const validation = await migrationService.validateSchema();
|
|
@@ -3613,6 +3786,8 @@ function renderLoginPage(data, demoLoginActive = false) {
|
|
|
3613
3786
|
<!-- Form -->
|
|
3614
3787
|
<form
|
|
3615
3788
|
id="login-form"
|
|
3789
|
+
action="/auth/login/form${data.redirect ? `?redirect=${encodeURIComponent(data.redirect)}` : ""}"
|
|
3790
|
+
method="post"
|
|
3616
3791
|
hx-post="/auth/login/form${data.redirect ? `?redirect=${encodeURIComponent(data.redirect)}` : ""}"
|
|
3617
3792
|
hx-target="#form-response"
|
|
3618
3793
|
hx-swap="innerHTML"
|
|
@@ -5128,9 +5303,11 @@ function renderDynamicField(field, options = {}) {
|
|
|
5128
5303
|
min="${opts.min || ""}"
|
|
5129
5304
|
max="${opts.max || ""}"
|
|
5130
5305
|
class="${baseClasses} ${errorClasses} !w-auto dark:[color-scheme:dark]"
|
|
5306
|
+
placeholder="YYYY-MM-DD"
|
|
5131
5307
|
${required}
|
|
5132
5308
|
${disabled ? "disabled" : ""}
|
|
5133
5309
|
>
|
|
5310
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Format: YYYY-MM-DD</p>
|
|
5134
5311
|
`;
|
|
5135
5312
|
break;
|
|
5136
5313
|
case "datetime":
|
|
@@ -5146,6 +5323,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5146
5323
|
${required}
|
|
5147
5324
|
${disabled ? "disabled" : ""}
|
|
5148
5325
|
>
|
|
5326
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Format: YYYY-MM-DD HH:MM</p>
|
|
5149
5327
|
`;
|
|
5150
5328
|
break;
|
|
5151
5329
|
case "slug":
|
|
@@ -7102,6 +7280,21 @@ function createUserProfilesPlugin() {
|
|
|
7102
7280
|
|
|
7103
7281
|
// src/routes/auth.ts
|
|
7104
7282
|
var JWT_SECRET_FALLBACK = "your-super-secret-jwt-key-change-in-production";
|
|
7283
|
+
var DEMO_EMAIL = "admin@sonicjs.com";
|
|
7284
|
+
var STATS_EVENTS_ENDPOINT = "https://stats.sonicjs.com/v1/events";
|
|
7285
|
+
async function trackDemoLogin(kv) {
|
|
7286
|
+
try {
|
|
7287
|
+
const installationId = await kv?.get("_sonicjs_installation_id") ?? "demo-unknown";
|
|
7288
|
+
await fetch(STATS_EVENTS_ENDPOINT, {
|
|
7289
|
+
method: "POST",
|
|
7290
|
+
headers: { "Content-Type": "application/json" },
|
|
7291
|
+
body: JSON.stringify({
|
|
7292
|
+
data: { installation_id: installationId, event_type: "demo_login", properties: {}, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
7293
|
+
})
|
|
7294
|
+
});
|
|
7295
|
+
} catch {
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7105
7298
|
function renderPasswordResetEmail(resetLink, firstName) {
|
|
7106
7299
|
const greeting = firstName ? `Hi ${firstName},` : "Hello,";
|
|
7107
7300
|
return `<!DOCTYPE html>
|
|
@@ -7293,8 +7486,8 @@ authRoutes.post(
|
|
|
7293
7486
|
}
|
|
7294
7487
|
const { email, password } = validation.data;
|
|
7295
7488
|
const normalizedEmail = email.toLowerCase();
|
|
7296
|
-
const { createAuth } = await import('./config-
|
|
7297
|
-
const auth = createAuth(c.env);
|
|
7489
|
+
const { createAuth } = await import('./config-4TIHSNW6.js');
|
|
7490
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7298
7491
|
const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
|
|
7299
7492
|
method: "POST",
|
|
7300
7493
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin },
|
|
@@ -7304,12 +7497,14 @@ authRoutes.post(
|
|
|
7304
7497
|
if (!baRes.ok) {
|
|
7305
7498
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
7306
7499
|
}
|
|
7307
|
-
|
|
7308
|
-
if (rawSetCookie) {
|
|
7309
|
-
c.res.headers.append("Set-Cookie", rawSetCookie);
|
|
7310
|
-
} else if (baRes.headers.getSetCookie) {
|
|
7500
|
+
if (baRes.headers.getSetCookie) {
|
|
7311
7501
|
for (const sc of baRes.headers.getSetCookie()) {
|
|
7312
|
-
c.
|
|
7502
|
+
c.header("Set-Cookie", sc, { append: true });
|
|
7503
|
+
}
|
|
7504
|
+
} else {
|
|
7505
|
+
const rawSetCookie = baRes.headers.get("set-cookie");
|
|
7506
|
+
if (rawSetCookie) {
|
|
7507
|
+
c.header("Set-Cookie", rawSetCookie, { append: true });
|
|
7313
7508
|
}
|
|
7314
7509
|
}
|
|
7315
7510
|
await setCsrfCookie(c);
|
|
@@ -7329,6 +7524,9 @@ authRoutes.post(
|
|
|
7329
7524
|
sameSite: "Strict",
|
|
7330
7525
|
maxAge: tokenTtl
|
|
7331
7526
|
});
|
|
7527
|
+
if (normalizedEmail === DEMO_EMAIL) {
|
|
7528
|
+
c.executionCtx?.waitUntil(trackDemoLogin(c.env.CACHE_KV));
|
|
7529
|
+
}
|
|
7332
7530
|
return c.json({
|
|
7333
7531
|
user: {
|
|
7334
7532
|
id: user.id,
|
|
@@ -7347,8 +7545,8 @@ authRoutes.post(
|
|
|
7347
7545
|
);
|
|
7348
7546
|
authRoutes.post("/logout", async (c) => {
|
|
7349
7547
|
try {
|
|
7350
|
-
const { createAuth } = await import('./config-
|
|
7351
|
-
const auth = createAuth(c.env);
|
|
7548
|
+
const { createAuth } = await import('./config-4TIHSNW6.js');
|
|
7549
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7352
7550
|
const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
|
|
7353
7551
|
method: "POST",
|
|
7354
7552
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin, "Cookie": c.req.header("Cookie") || "" },
|
|
@@ -7357,14 +7555,15 @@ authRoutes.post("/logout", async (c) => {
|
|
|
7357
7555
|
await auth.handler(baReq);
|
|
7358
7556
|
} catch {
|
|
7359
7557
|
}
|
|
7360
|
-
|
|
7558
|
+
const isSecure = new URL(c.req.url).protocol === "https:";
|
|
7559
|
+
setCookie(c, "better-auth.session_token", "", { httpOnly: true, sameSite: "Lax", path: "/", maxAge: 0, secure: isSecure });
|
|
7361
7560
|
clearCsrfCookie(c);
|
|
7362
7561
|
return c.json({ message: "Logged out successfully" });
|
|
7363
7562
|
});
|
|
7364
7563
|
authRoutes.get("/logout", async (c) => {
|
|
7365
7564
|
try {
|
|
7366
|
-
const { createAuth } = await import('./config-
|
|
7367
|
-
const auth = createAuth(c.env);
|
|
7565
|
+
const { createAuth } = await import('./config-4TIHSNW6.js');
|
|
7566
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7368
7567
|
const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
|
|
7369
7568
|
method: "POST",
|
|
7370
7569
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin, "Cookie": c.req.header("Cookie") || "" },
|
|
@@ -7373,7 +7572,8 @@ authRoutes.get("/logout", async (c) => {
|
|
|
7373
7572
|
await auth.handler(baReq);
|
|
7374
7573
|
} catch {
|
|
7375
7574
|
}
|
|
7376
|
-
|
|
7575
|
+
const isSecure = new URL(c.req.url).protocol === "https:";
|
|
7576
|
+
setCookie(c, "better-auth.session_token", "", { httpOnly: true, sameSite: "Lax", path: "/", maxAge: 0, secure: isSecure });
|
|
7377
7577
|
clearCsrfCookie(c);
|
|
7378
7578
|
return c.redirect("/auth/login?message=You have been logged out successfully");
|
|
7379
7579
|
});
|
|
@@ -7570,8 +7770,8 @@ authRoutes.post(
|
|
|
7570
7770
|
</div>
|
|
7571
7771
|
`);
|
|
7572
7772
|
}
|
|
7573
|
-
const { createAuth } = await import('./config-
|
|
7574
|
-
const auth = createAuth(c.env);
|
|
7773
|
+
const { createAuth } = await import('./config-4TIHSNW6.js');
|
|
7774
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7575
7775
|
const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
|
|
7576
7776
|
method: "POST",
|
|
7577
7777
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin },
|
|
@@ -7585,17 +7785,27 @@ authRoutes.post(
|
|
|
7585
7785
|
</div>
|
|
7586
7786
|
`);
|
|
7587
7787
|
}
|
|
7588
|
-
|
|
7589
|
-
if (rawSetCookie) {
|
|
7590
|
-
c.res.headers.append("Set-Cookie", rawSetCookie);
|
|
7591
|
-
} else if (baRes.headers.getSetCookie) {
|
|
7788
|
+
if (baRes.headers.getSetCookie) {
|
|
7592
7789
|
for (const sc of baRes.headers.getSetCookie()) {
|
|
7593
|
-
c.
|
|
7790
|
+
c.header("Set-Cookie", sc, { append: true });
|
|
7791
|
+
}
|
|
7792
|
+
} else {
|
|
7793
|
+
const rawSetCookie = baRes.headers.get("set-cookie");
|
|
7794
|
+
if (rawSetCookie) {
|
|
7795
|
+
c.header("Set-Cookie", rawSetCookie, { append: true });
|
|
7594
7796
|
}
|
|
7595
7797
|
}
|
|
7596
7798
|
await setCsrfCookie(c);
|
|
7799
|
+
if (email === DEMO_EMAIL) {
|
|
7800
|
+
c.executionCtx?.waitUntil(trackDemoLogin(c.env.CACHE_KV));
|
|
7801
|
+
}
|
|
7597
7802
|
const rawRedirect = c.req.query("redirect");
|
|
7598
7803
|
const redirectUrl = rawRedirect && rawRedirect.startsWith("/") ? rawRedirect : "/admin/content";
|
|
7804
|
+
const isHtmx = c.req.header("HX-Request") === "true";
|
|
7805
|
+
if (isHtmx) {
|
|
7806
|
+
c.header("HX-Redirect", redirectUrl);
|
|
7807
|
+
return c.html(html`<div id="form-response"><p class="text-sm text-green-600">Login successful! Redirecting...</p></div>`);
|
|
7808
|
+
}
|
|
7599
7809
|
return c.html(html`
|
|
7600
7810
|
<div id="form-response">
|
|
7601
7811
|
<div class="rounded-lg bg-green-100 dark:bg-lime-500/10 p-4 ring-1 ring-green-400 dark:ring-lime-500/20">
|
|
@@ -9107,33 +9317,44 @@ function renderContentFormPage(data, opts) {
|
|
|
9107
9317
|
</a>
|
|
9108
9318
|
|
|
9109
9319
|
<div class="flex items-center gap-x-3">
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
>
|
|
9118
|
-
<
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
</button>
|
|
9123
|
-
` : ""}
|
|
9320
|
+
<button
|
|
9321
|
+
type="submit"
|
|
9322
|
+
form="content-form"
|
|
9323
|
+
name="action"
|
|
9324
|
+
value="save"
|
|
9325
|
+
class="inline-flex items-center justify-center gap-x-1.5 rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm"
|
|
9326
|
+
>
|
|
9327
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
9328
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
|
9329
|
+
</svg>
|
|
9330
|
+
Save
|
|
9331
|
+
</button>
|
|
9124
9332
|
|
|
9125
|
-
|
|
9333
|
+
<button
|
|
9334
|
+
type="submit"
|
|
9335
|
+
form="content-form"
|
|
9336
|
+
name="action"
|
|
9337
|
+
value="save_and_close"
|
|
9338
|
+
class="inline-flex items-center justify-center gap-x-1.5 rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm"
|
|
9339
|
+
>
|
|
9340
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
9341
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
9342
|
+
</svg>
|
|
9343
|
+
Save & close
|
|
9344
|
+
</button>
|
|
9345
|
+
|
|
9346
|
+
${data.user?.role !== "viewer" && data.versioningEnabled ? `
|
|
9126
9347
|
<button
|
|
9127
9348
|
type="submit"
|
|
9128
9349
|
form="content-form"
|
|
9129
9350
|
name="action"
|
|
9130
9351
|
value="save_and_publish"
|
|
9131
|
-
class="inline-flex items-center justify-center gap-x-1.5 rounded-lg
|
|
9352
|
+
class="inline-flex items-center justify-center gap-x-1.5 rounded-lg bg-lime-600 dark:bg-lime-500 hover:bg-lime-700 dark:hover:bg-lime-600 px-3.5 py-2.5 text-sm font-semibold text-white transition-colors shadow-sm"
|
|
9132
9353
|
>
|
|
9133
9354
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
9134
9355
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
9135
9356
|
</svg>
|
|
9136
|
-
|
|
9357
|
+
Publish
|
|
9137
9358
|
</button>
|
|
9138
9359
|
` : ""}
|
|
9139
9360
|
</div>
|
|
@@ -12276,6 +12497,9 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
12276
12497
|
}), user?.userId);
|
|
12277
12498
|
const cache2 = getCacheService(CACHE_CONFIGS.content);
|
|
12278
12499
|
await cache2.invalidate(`content:list:${collectionId}:*`);
|
|
12500
|
+
const apiCacheCreate = getCacheService(CACHE_CONFIGS.api);
|
|
12501
|
+
await apiCacheCreate.invalidate("api:content-filtered:*");
|
|
12502
|
+
await apiCacheCreate.invalidate(`api:collection-content-filtered:${createDocType.id}:*`);
|
|
12279
12503
|
const redirectUrl2 = `/admin/content/${doc.rootId}/edit?success=Content created successfully!`;
|
|
12280
12504
|
return c.req.header("HX-Request") === "true" ? c.text("", 200, { "HX-Redirect": redirectUrl2 }) : c.redirect(redirectUrl2);
|
|
12281
12505
|
}
|
|
@@ -12394,8 +12618,28 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
12394
12618
|
if (status2 === "published") await svc.publish(newDraft.id, user?.userId);
|
|
12395
12619
|
else if (pub) await svc.unpublish(pub.id);
|
|
12396
12620
|
await getCacheService(CACHE_CONFIGS.content).invalidate(`content:list:*`);
|
|
12621
|
+
const apiCacheEdit = getCacheService(CACHE_CONFIGS.api);
|
|
12622
|
+
await apiCacheEdit.invalidate("api:content-filtered:*");
|
|
12623
|
+
await apiCacheEdit.invalidate(`api:collection-content-filtered:${docRowU.type_id}:*`);
|
|
12624
|
+
const isHTMX2 = c.req.header("HX-Request") === "true";
|
|
12625
|
+
const referrerParams = formData.get("referrer_params");
|
|
12626
|
+
const listUrl = referrerParams ? `/admin/content?${referrerParams}` : `/admin/content?collection=${dcoll.id}`;
|
|
12627
|
+
if (action === "save_and_close") {
|
|
12628
|
+
return isHTMX2 ? c.text("", 200, { "HX-Redirect": listUrl }) : c.redirect(listUrl);
|
|
12629
|
+
}
|
|
12630
|
+
if (isHTMX2) {
|
|
12631
|
+
const label = status2 === "published" ? "Content published successfully!" : "Content saved successfully!";
|
|
12632
|
+
return c.html(html`<div id="form-messages">
|
|
12633
|
+
<div class="rounded-lg bg-green-50 dark:bg-green-900/20 p-4 mb-4 flex items-center gap-3 border border-green-200 dark:border-green-800">
|
|
12634
|
+
<svg class="h-5 w-5 text-green-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
12635
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
12636
|
+
</svg>
|
|
12637
|
+
<span class="text-sm font-medium text-green-800 dark:text-green-200">${label}</span>
|
|
12638
|
+
</div>
|
|
12639
|
+
</div>`);
|
|
12640
|
+
}
|
|
12397
12641
|
const redirectUrl2 = `/admin/content/${id}/edit?success=Content updated successfully!`;
|
|
12398
|
-
return c.
|
|
12642
|
+
return c.redirect(redirectUrl2);
|
|
12399
12643
|
}
|
|
12400
12644
|
}
|
|
12401
12645
|
return c.html(html`
|
|
@@ -12789,8 +13033,8 @@ adminContentRoutes.post("/bulk-action", async (c) => {
|
|
|
12789
13033
|
}
|
|
12790
13034
|
await cache.invalidate("content:list:*");
|
|
12791
13035
|
const apiCache = getCacheService(CACHE_CONFIGS.api);
|
|
12792
|
-
await apiCache.invalidate("content-filtered:*");
|
|
12793
|
-
await apiCache.invalidate("collection-content-filtered:*");
|
|
13036
|
+
await apiCache.invalidate("api:content-filtered:*");
|
|
13037
|
+
await apiCache.invalidate("api:collection-content-filtered:*");
|
|
12794
13038
|
return c.json({ success: true, count: ids.length });
|
|
12795
13039
|
} catch (error) {
|
|
12796
13040
|
console.error("Bulk action error:", error);
|
|
@@ -12808,6 +13052,9 @@ adminContentRoutes.delete("/:id", async (c) => {
|
|
|
12808
13052
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
12809
13053
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = ?").bind(now2, now2, id, tenantId).run();
|
|
12810
13054
|
await getCacheService(CACHE_CONFIGS.content).invalidate("content:list:*");
|
|
13055
|
+
const apiCacheDel = getCacheService(CACHE_CONFIGS.api);
|
|
13056
|
+
await apiCacheDel.invalidate("api:content-filtered:*");
|
|
13057
|
+
await apiCacheDel.invalidate(`api:collection-content-filtered:${docDel.type_id}:*`);
|
|
12811
13058
|
return c.html(`
|
|
12812
13059
|
<div id="content-list" hx-get="/admin/content?model=${docDel.type_id}" hx-trigger="load" hx-swap="outerHTML">
|
|
12813
13060
|
<div class="flex items-center justify-center p-8"><span class="text-zinc-500">Deleting\u2026</span></div>
|
|
@@ -13103,7 +13350,7 @@ adminContentRoutes.post("/documents/:typeId/:documentId/delete", async (c) => {
|
|
|
13103
13350
|
adminContentRoutes.get("/documents/:typeId/:rootId/versions", async (c) => {
|
|
13104
13351
|
const { typeId, rootId } = c.req.param();
|
|
13105
13352
|
const db = c.env.DB;
|
|
13106
|
-
const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-
|
|
13353
|
+
const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-JWV5GITD.js');
|
|
13107
13354
|
const registry = new DocumentTypeRegistry(db);
|
|
13108
13355
|
const docType = await registry.findById(typeId);
|
|
13109
13356
|
if (!docType) return c.html("<div>Unknown type.</div>", 404);
|
|
@@ -15358,12 +15605,10 @@ var LANGUAGES = [
|
|
|
15358
15605
|
{ value: "ko", label: "Korean" },
|
|
15359
15606
|
{ value: "zh", label: "Chinese" }
|
|
15360
15607
|
];
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
{ value:
|
|
15364
|
-
|
|
15365
|
-
{ value: "viewer", label: "Viewer" }
|
|
15366
|
-
];
|
|
15608
|
+
async function fetchRoleOptions(db) {
|
|
15609
|
+
const roles = await new RbacService(db).getRoles();
|
|
15610
|
+
return roles.map((r) => ({ value: r.id, label: r.display_name }));
|
|
15611
|
+
}
|
|
15367
15612
|
userRoutes.get("/profile", async (c) => {
|
|
15368
15613
|
const user = c.get("user");
|
|
15369
15614
|
const db = c.env.DB;
|
|
@@ -15812,8 +16057,9 @@ userRoutes.get("/users/new", async (c) => {
|
|
|
15812
16057
|
const regFieldNames = profileConfig?.registrationFields ?? [];
|
|
15813
16058
|
const regFields = profileConfig && upRow ? regFieldNames.length > 0 ? profileConfig.fields.filter((f) => regFieldNames.includes(f.name)) : profileConfig.fields : [];
|
|
15814
16059
|
const registrationFieldsHtml = regFields.length > 0 ? renderCustomProfileSection({ fields: regFields }, {}) : void 0;
|
|
16060
|
+
const roles = await fetchRoleOptions(db);
|
|
15815
16061
|
const pageData = {
|
|
15816
|
-
roles
|
|
16062
|
+
roles,
|
|
15817
16063
|
registrationFieldsHtml,
|
|
15818
16064
|
user: {
|
|
15819
16065
|
name: user.email.split("@")[0] || user.email,
|
|
@@ -15841,9 +16087,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
15841
16087
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
15842
16088
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
15843
16089
|
const bio = sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
15844
|
-
const roleInput = formData.get("role")?.toString() || "
|
|
15845
|
-
const validRoles =
|
|
15846
|
-
const role = validRoles.includes(roleInput) ? roleInput : "
|
|
16090
|
+
const roleInput = formData.get("role")?.toString() || "";
|
|
16091
|
+
const validRoles = (await new RbacService(db).getRoles()).map((r) => r.id);
|
|
16092
|
+
const role = validRoles.includes(roleInput) ? roleInput : validRoles[0] ?? "admin";
|
|
15847
16093
|
const password = formData.get("password")?.toString() || "";
|
|
15848
16094
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
15849
16095
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -16065,9 +16311,10 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
16065
16311
|
}
|
|
16066
16312
|
const queryMessage = c.req.query("message");
|
|
16067
16313
|
const queryType = c.req.query("type") === "error" ? "error" : "success";
|
|
16314
|
+
const roles = await fetchRoleOptions(db);
|
|
16068
16315
|
const pageData = {
|
|
16069
16316
|
userToEdit: editData,
|
|
16070
|
-
roles
|
|
16317
|
+
roles,
|
|
16071
16318
|
hasProfilePlugin: profileConfig !== null && upRow !== null,
|
|
16072
16319
|
customProfileFieldsHtml,
|
|
16073
16320
|
tenantMemberships,
|
|
@@ -16099,9 +16346,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
16099
16346
|
const lastName = sanitizeInput(formData.get("last_name")?.toString());
|
|
16100
16347
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
16101
16348
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
16102
|
-
const roleInput = formData.get("role")?.toString() || "
|
|
16103
|
-
const validRoles =
|
|
16104
|
-
const role = validRoles.includes(roleInput) ? roleInput : "
|
|
16349
|
+
const roleInput = formData.get("role")?.toString() || "";
|
|
16350
|
+
const validRoles = (await new RbacService(db).getRoles()).map((r) => r.id);
|
|
16351
|
+
const role = validRoles.includes(roleInput) ? roleInput : validRoles[0] ?? "admin";
|
|
16105
16352
|
const isActive = formData.get("is_active") === "1";
|
|
16106
16353
|
const emailVerified = formData.get("email_verified") === "1";
|
|
16107
16354
|
const newPassword = formData.get("new_password")?.toString() || "";
|
|
@@ -18688,15 +18935,6 @@ function renderPluginsListPage(data) {
|
|
|
18688
18935
|
</div>
|
|
18689
18936
|
|
|
18690
18937
|
<div class="flex items-center gap-3 w-full sm:w-auto">
|
|
18691
|
-
<select id="sort-filter" onchange="filterAndSortPlugins()" class="block w-full sm:w-auto h-9 rounded-md border-0 py-1.5 pl-3 pr-8 text-zinc-900 ring-1 ring-inset ring-zinc-300 focus:ring-2 focus:ring-inset focus:ring-zinc-600 dark:bg-zinc-900 dark:text-white dark:ring-zinc-700 dark:focus:ring-zinc-500 sm:text-sm sm:leading-6">
|
|
18692
|
-
<option value="name-asc">Name (A-Z)</option>
|
|
18693
|
-
<option value="name-desc">Name (Z-A)</option>
|
|
18694
|
-
<option value="newest">Newest Installed</option>
|
|
18695
|
-
<option value="updated">Recently Updated</option>
|
|
18696
|
-
<option value="popular">Popularity</option>
|
|
18697
|
-
<option value="rating">Highest Rated</option>
|
|
18698
|
-
</select>
|
|
18699
|
-
|
|
18700
18938
|
<button
|
|
18701
18939
|
onclick="location.reload()"
|
|
18702
18940
|
class="inline-flex items-center gap-x-1.5 rounded-md bg-white dark:bg-zinc-900 px-3 py-1.5 h-9 text-sm font-semibold text-zinc-900 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800"
|
|
@@ -18860,8 +19098,6 @@ function renderPluginsListPage(data) {
|
|
|
18860
19098
|
.forEach(cb => params.append('status', cb.value));
|
|
18861
19099
|
const search = document.getElementById('search-input').value;
|
|
18862
19100
|
if (search) params.set('search', search);
|
|
18863
|
-
const sort = document.getElementById('sort-filter').value;
|
|
18864
|
-
if (sort && sort !== 'name-asc') params.set('sort', sort);
|
|
18865
19101
|
const url = new URL(window.location.href);
|
|
18866
19102
|
url.search = params.toString();
|
|
18867
19103
|
history.replaceState(null, '', url.toString());
|
|
@@ -18879,8 +19115,6 @@ function renderPluginsListPage(data) {
|
|
|
18879
19115
|
});
|
|
18880
19116
|
const search = params.get('search');
|
|
18881
19117
|
if (search) document.getElementById('search-input').value = search;
|
|
18882
|
-
const sort = params.get('sort');
|
|
18883
|
-
if (sort) document.getElementById('sort-filter').value = sort;
|
|
18884
19118
|
if (params.toString()) filterAndSortPlugins();
|
|
18885
19119
|
}
|
|
18886
19120
|
|
|
@@ -18896,7 +19130,6 @@ function renderPluginsListPage(data) {
|
|
|
18896
19130
|
.map(cb => cb.value.toLowerCase());
|
|
18897
19131
|
|
|
18898
19132
|
const searchInput = document.getElementById('search-input').value.toLowerCase();
|
|
18899
|
-
const sortValue = document.getElementById('sort-filter').value;
|
|
18900
19133
|
|
|
18901
19134
|
const pluginsGrid = document.getElementById('plugins-grid');
|
|
18902
19135
|
const pluginCards = Array.from(pluginsGrid.querySelectorAll('.plugin-card'));
|
|
@@ -18920,30 +19153,6 @@ function renderPluginsListPage(data) {
|
|
|
18920
19153
|
return true;
|
|
18921
19154
|
});
|
|
18922
19155
|
|
|
18923
|
-
// Sort
|
|
18924
|
-
visibleCards.sort((a, b) => {
|
|
18925
|
-
const aName = a.getAttribute('data-name') || '';
|
|
18926
|
-
const bName = b.getAttribute('data-name') || '';
|
|
18927
|
-
const aInstalled = parseInt(a.getAttribute('data-installed') || '0');
|
|
18928
|
-
const bInstalled = parseInt(b.getAttribute('data-installed') || '0');
|
|
18929
|
-
const aUpdated = parseInt(a.getAttribute('data-updated') || '0');
|
|
18930
|
-
const bUpdated = parseInt(b.getAttribute('data-updated') || '0');
|
|
18931
|
-
const aDownloads = parseInt(a.getAttribute('data-downloads') || '0');
|
|
18932
|
-
const bDownloads = parseInt(b.getAttribute('data-downloads') || '0');
|
|
18933
|
-
const aRating = parseFloat(a.getAttribute('data-rating') || '0');
|
|
18934
|
-
const bRating = parseFloat(b.getAttribute('data-rating') || '0');
|
|
18935
|
-
|
|
18936
|
-
switch (sortValue) {
|
|
18937
|
-
case 'name-desc': return bName.localeCompare(aName);
|
|
18938
|
-
case 'newest': return bInstalled - aInstalled;
|
|
18939
|
-
case 'updated': return bUpdated - aUpdated;
|
|
18940
|
-
case 'popular': return bDownloads - aDownloads;
|
|
18941
|
-
case 'rating': return bRating - aRating;
|
|
18942
|
-
case 'name-asc':
|
|
18943
|
-
default: return aName.localeCompare(bName);
|
|
18944
|
-
}
|
|
18945
|
-
});
|
|
18946
|
-
|
|
18947
19156
|
// Re-append
|
|
18948
19157
|
pluginCards.forEach(card => card.style.display = 'none'); // Hide all first
|
|
18949
19158
|
|
|
@@ -19378,7 +19587,8 @@ function escapeHtmlAttr(value) {
|
|
|
19378
19587
|
function renderPluginSettingsPage(data) {
|
|
19379
19588
|
const { plugin, activity = [], user, settingsTabData } = data;
|
|
19380
19589
|
const pluginDef = getPluginDefinition(plugin.id || plugin.name);
|
|
19381
|
-
const
|
|
19590
|
+
const pluginId = plugin.id || plugin.name;
|
|
19591
|
+
const hasUserSettings = pluginDef?.settingsTabContent != null || Object.keys(plugin.settings || {}).some((k) => !k.startsWith("_")) || hasCustomSettingsComponent(pluginId);
|
|
19382
19592
|
const defaultTab = hasUserSettings ? "settings" : "info";
|
|
19383
19593
|
const pageContent = `
|
|
19384
19594
|
<div class="w-full px-4 sm:px-6 lg:px-8 py-6">
|
|
@@ -19543,9 +19753,28 @@ function renderPluginSettingsPage(data) {
|
|
|
19543
19753
|
const form = document.getElementById('settings-form');
|
|
19544
19754
|
const formData = new FormData(form);
|
|
19545
19755
|
const isAuthPlugin = '${plugin.id}' === 'core-auth';
|
|
19756
|
+
const isOAuthPlugin = '${plugin.id}' === 'oauth-providers';
|
|
19546
19757
|
let settings = {};
|
|
19547
19758
|
|
|
19548
|
-
if (
|
|
19759
|
+
if (isOAuthPlugin) {
|
|
19760
|
+
// Build nested provider settings structure
|
|
19761
|
+
const getVal = id => (document.getElementById(id) || {}).value || '';
|
|
19762
|
+
const getChecked = id => !!(document.getElementById(id) || {}).checked;
|
|
19763
|
+
settings = {
|
|
19764
|
+
providers: {
|
|
19765
|
+
github: {
|
|
19766
|
+
clientId: getVal('oauth_github_clientId'),
|
|
19767
|
+
clientSecret: getVal('oauth_github_clientSecret'),
|
|
19768
|
+
enabled: getChecked('oauth_github_enabled'),
|
|
19769
|
+
},
|
|
19770
|
+
google: {
|
|
19771
|
+
clientId: getVal('oauth_google_clientId'),
|
|
19772
|
+
clientSecret: getVal('oauth_google_clientSecret'),
|
|
19773
|
+
enabled: getChecked('oauth_google_enabled'),
|
|
19774
|
+
},
|
|
19775
|
+
}
|
|
19776
|
+
};
|
|
19777
|
+
} else if (isAuthPlugin) {
|
|
19549
19778
|
// Handle nested auth settings structure
|
|
19550
19779
|
settings = {
|
|
19551
19780
|
requiredFields: {},
|
|
@@ -20033,7 +20262,8 @@ function formatTimestamp(timestamp) {
|
|
|
20033
20262
|
}
|
|
20034
20263
|
var pluginSettingsComponents = {
|
|
20035
20264
|
"otp-login": renderOTPLoginSettingsContent,
|
|
20036
|
-
"email": renderEmailSettingsContent
|
|
20265
|
+
"email": renderEmailSettingsContent,
|
|
20266
|
+
"oauth-providers": renderOAuthProvidersSettingsContent
|
|
20037
20267
|
};
|
|
20038
20268
|
function renderOTPLoginSettingsContent(plugin, settings) {
|
|
20039
20269
|
const siteName = settings.siteName || "SonicJS";
|
|
@@ -20756,6 +20986,62 @@ defineUserProfile({
|
|
|
20756
20986
|
</div>
|
|
20757
20987
|
`;
|
|
20758
20988
|
}
|
|
20989
|
+
function renderOAuthProvidersSettingsContent(plugin, settings) {
|
|
20990
|
+
const providers = settings.providers || {};
|
|
20991
|
+
const gh = providers.github || {};
|
|
20992
|
+
const gg = providers.google || {};
|
|
20993
|
+
const inputClass = "backdrop-blur-sm bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-gray-300 focus:border-blue-400 focus:outline-none transition-colors w-full";
|
|
20994
|
+
return `
|
|
20995
|
+
<div class="space-y-6">
|
|
20996
|
+
<form id="settings-form">
|
|
20997
|
+
<div class="backdrop-blur-md bg-white/5 rounded-xl border border-white/10 p-6 space-y-6">
|
|
20998
|
+
<h3 class="text-lg font-semibold text-white">GitHub OAuth</h3>
|
|
20999
|
+
<div>
|
|
21000
|
+
<label for="oauth_github_clientId" class="block text-sm font-medium text-gray-300 mb-2">Client ID</label>
|
|
21001
|
+
<input type="text" id="oauth_github_clientId" name="oauth_github_clientId"
|
|
21002
|
+
value="${escapeHtmlAttr(gh.clientId || "")}"
|
|
21003
|
+
placeholder="GitHub OAuth App Client ID" class="${inputClass}" />
|
|
21004
|
+
</div>
|
|
21005
|
+
<div>
|
|
21006
|
+
<label for="oauth_github_clientSecret" class="block text-sm font-medium text-gray-300 mb-2">Client Secret</label>
|
|
21007
|
+
<input type="password" id="oauth_github_clientSecret" name="oauth_github_clientSecret"
|
|
21008
|
+
value="${escapeHtmlAttr(gh.clientSecret || "")}"
|
|
21009
|
+
placeholder="GitHub OAuth App Client Secret" class="${inputClass}" />
|
|
21010
|
+
</div>
|
|
21011
|
+
<div class="flex items-center gap-3">
|
|
21012
|
+
<input type="checkbox" id="oauth_github_enabled" name="oauth_github_enabled"
|
|
21013
|
+
${gh.enabled ? "checked" : ""} class="w-4 h-4 rounded border-white/10" />
|
|
21014
|
+
<label for="oauth_github_enabled" class="text-sm text-gray-300">Enable GitHub login</label>
|
|
21015
|
+
</div>
|
|
21016
|
+
</div>
|
|
21017
|
+
|
|
21018
|
+
<div class="backdrop-blur-md bg-white/5 rounded-xl border border-white/10 p-6 space-y-6 mt-6">
|
|
21019
|
+
<h3 class="text-lg font-semibold text-white">Google OAuth</h3>
|
|
21020
|
+
<div>
|
|
21021
|
+
<label for="oauth_google_clientId" class="block text-sm font-medium text-gray-300 mb-2">Client ID</label>
|
|
21022
|
+
<input type="text" id="oauth_google_clientId" name="oauth_google_clientId"
|
|
21023
|
+
value="${escapeHtmlAttr(gg.clientId || "")}"
|
|
21024
|
+
placeholder="Google OAuth Client ID" class="${inputClass}" />
|
|
21025
|
+
</div>
|
|
21026
|
+
<div>
|
|
21027
|
+
<label for="oauth_google_clientSecret" class="block text-sm font-medium text-gray-300 mb-2">Client Secret</label>
|
|
21028
|
+
<input type="password" id="oauth_google_clientSecret" name="oauth_google_clientSecret"
|
|
21029
|
+
value="${escapeHtmlAttr(gg.clientSecret || "")}"
|
|
21030
|
+
placeholder="Google OAuth Client Secret" class="${inputClass}" />
|
|
21031
|
+
</div>
|
|
21032
|
+
<div class="flex items-center gap-3">
|
|
21033
|
+
<input type="checkbox" id="oauth_google_enabled" name="oauth_google_enabled"
|
|
21034
|
+
${gg.enabled ? "checked" : ""} class="w-4 h-4 rounded border-white/10" />
|
|
21035
|
+
<label for="oauth_google_enabled" class="text-sm text-gray-300">Enable Google login</label>
|
|
21036
|
+
</div>
|
|
21037
|
+
</div>
|
|
21038
|
+
</form>
|
|
21039
|
+
</div>
|
|
21040
|
+
`;
|
|
21041
|
+
}
|
|
21042
|
+
function hasCustomSettingsComponent(pluginId) {
|
|
21043
|
+
return pluginId in pluginSettingsComponents;
|
|
21044
|
+
}
|
|
20759
21045
|
|
|
20760
21046
|
// src/plugins/core-plugins/menu-plugin/services/menu-reconcile.ts
|
|
20761
21047
|
async function upsertPluginRow(db, slug, entry, visible) {
|
|
@@ -27117,5 +27403,5 @@ var ROUTES_INFO = {
|
|
|
27117
27403
|
};
|
|
27118
27404
|
|
|
27119
27405
|
export { DocumentRepository, ROUTES_INFO, TenantService, VALID_MEMBER_ROLES, adminCheckboxRoutes, adminCollectionsRoutes, adminDocumentsRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_content_default, apiDocumentsRoutes, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, createDocumentSchema, createUserProfilesPlugin, defineUserProfile, denyIfNotAllowed, dispatchHookEvent, effectiveTenantForType, getConfirmationDialogScript2 as getConfirmationDialogScript, getCustomData, getDocumentRequestContext, getEmailService, getUserProfileConfig, getVariablesCached, hasEmailService, invalidateVariablesCache, isValidMemberRole, lexicalEditorPlugin, reconcileMenuFromPlugins, renderConfirmationDialog2 as renderConfirmationDialog, renderTable2 as renderTable, resolveDocScope, resolveVariablesInObject, router, router2, setEmailService, setVariablesCache, test_cleanup_default, updateDocumentSchema, userProfilesPlugin, userRoutes };
|
|
27120
|
-
//# sourceMappingURL=chunk-
|
|
27121
|
-
//# sourceMappingURL=chunk-
|
|
27406
|
+
//# sourceMappingURL=chunk-7P5OCUPS.js.map
|
|
27407
|
+
//# sourceMappingURL=chunk-7P5OCUPS.js.map
|