@sonicjs-cms/core 3.0.0-beta.23 → 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-KS24NPPL.js → chunk-2D3A4X52.js} +21 -2
- package/dist/chunk-2D3A4X52.js.map +1 -0
- package/dist/{chunk-DIBCKCUE.js → chunk-2OXFMMA3.js} +14 -29
- 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-CTYLCWMO.cjs → chunk-6OA77UM7.cjs} +802 -481
- 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-SK5CHFJP.js → chunk-7P5OCUPS.js} +467 -149
- 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-C3ZQP5NW.cjs → chunk-BJ6NJL2E.cjs} +7 -12
- package/dist/chunk-BJ6NJL2E.cjs.map +1 -0
- package/dist/{chunk-2MIYYAO4.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-DPLJYTHE.js → chunk-CWIGQYRR.js} +7 -12
- package/dist/chunk-CWIGQYRR.js.map +1 -0
- package/dist/{chunk-4WMZGHIC.cjs → chunk-D6FWEYQE.cjs} +6 -5
- package/dist/chunk-D6FWEYQE.cjs.map +1 -0
- package/dist/{chunk-A46IRC5A.js → chunk-D7RLBBHE.js} +2 -2
- package/dist/{chunk-A46IRC5A.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-IS54FPB7.cjs → chunk-E7E3NL3Y.cjs} +27 -42
- package/dist/chunk-E7E3NL3Y.cjs.map +1 -0
- package/dist/{chunk-XXRR54AB.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-RFBW5RRH.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-VGHEKIJL.cjs → chunk-MOGK3U4A.cjs} +2 -2
- package/dist/{chunk-VGHEKIJL.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-AT4ZQGEZ.cjs → chunk-YAURVGUK.cjs} +21 -2
- package/dist/chunk-YAURVGUK.cjs.map +1 -0
- 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 +1220 -353
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -3
- package/dist/index.d.ts +38 -3
- package/dist/index.js +895 -30
- 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-XQ6EX6HX.js.map → migrations-JAHMI5MK.js.map} +1 -1
- package/dist/migrations-KU4V6T73.cjs +13 -0
- package/dist/{migrations-ZD7Y5NUC.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-2MIYYAO4.js.map +0 -1
- package/dist/chunk-3TMTYJAJ.js.map +0 -1
- package/dist/chunk-4WMZGHIC.cjs.map +0 -1
- package/dist/chunk-AT4ZQGEZ.cjs.map +0 -1
- package/dist/chunk-BJROXF4N.js.map +0 -1
- package/dist/chunk-C3ZQP5NW.cjs.map +0 -1
- package/dist/chunk-CTYLCWMO.cjs.map +0 -1
- package/dist/chunk-DIBCKCUE.js.map +0 -1
- package/dist/chunk-DPLJYTHE.js.map +0 -1
- package/dist/chunk-E66UVONV.cjs.map +0 -1
- package/dist/chunk-IS54FPB7.cjs.map +0 -1
- package/dist/chunk-IXUHXTHW.cjs.map +0 -1
- package/dist/chunk-KS24NPPL.js.map +0 -1
- package/dist/chunk-LTZ5WYOK.cjs.map +0 -1
- package/dist/chunk-QZGABF2M.js.map +0 -1
- package/dist/chunk-RFBW5RRH.cjs.map +0 -1
- package/dist/chunk-SK5CHFJP.js.map +0 -1
- package/dist/chunk-THFZSDIU.js.map +0 -1
- package/dist/chunk-UYJQDDEE.cjs.map +0 -1
- package/dist/chunk-XXRR54AB.js.map +0 -1
- package/dist/config-6FB2BXMP.js +0 -6
- package/dist/migrations-XQ6EX6HX.js +0 -4
- package/dist/migrations-ZD7Y5NUC.cjs +0 -13
- 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
|
}
|
|
@@ -149,6 +150,18 @@ var DocumentRepository = class {
|
|
|
149
150
|
const row = await this.db.prepare("SELECT * FROM documents WHERE root_id = ? AND tenant_id = ? AND is_published = 1").bind(rootId, this.tenantId).first();
|
|
150
151
|
return row ? rowToDocument(row) : null;
|
|
151
152
|
}
|
|
153
|
+
// Slug lookup for a single doc. Prefer the published row; fall back to the current
|
|
154
|
+
// draft so a never-published doc is still reachable by slug (matches getById, which
|
|
155
|
+
// resolves drafts too). ORDER BY makes the preference deterministic when both exist.
|
|
156
|
+
async getBySlug(typeId, slug) {
|
|
157
|
+
const row = await this.db.prepare(
|
|
158
|
+
`SELECT * FROM documents
|
|
159
|
+
WHERE tenant_id = ? AND type_id = ? AND slug = ? AND deleted_at IS NULL
|
|
160
|
+
AND (is_published = 1 OR is_current_draft = 1)
|
|
161
|
+
ORDER BY is_published DESC LIMIT 1`
|
|
162
|
+
).bind(this.tenantId, typeId, slug).first();
|
|
163
|
+
return row ? rowToDocument(row) : null;
|
|
164
|
+
}
|
|
152
165
|
// Unified, tenant-scoped list with optional generated-column / facet filters and sort. This is the
|
|
153
166
|
// single place document list SQL is built — route handlers must call this, never inline SQL (R4).
|
|
154
167
|
async list(opts = {}) {
|
|
@@ -982,6 +995,32 @@ apiContentCrudRoutes.get("/:id", optionalAuth(), async (c) => {
|
|
|
982
995
|
{ collection: docRow.type_id, id: docRow.root_id, data: transformedContent.data },
|
|
983
996
|
"fire-and-forget"
|
|
984
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
|
+
}
|
|
985
1024
|
return c.json({ data: transformedContent });
|
|
986
1025
|
} catch (error) {
|
|
987
1026
|
console.error("Error fetching content:", error);
|
|
@@ -1045,8 +1084,8 @@ apiContentCrudRoutes.post("/", requireAuth(), requireRole(["admin", "editor", "a
|
|
|
1045
1084
|
user?.userId
|
|
1046
1085
|
);
|
|
1047
1086
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1048
|
-
await cache.invalidate("content-filtered:*");
|
|
1049
|
-
await cache.invalidate(
|
|
1087
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1088
|
+
await cache.invalidate(`api:collection-content-filtered:${backing.coll.name}:*`);
|
|
1050
1089
|
dispatchHookEvent(
|
|
1051
1090
|
c,
|
|
1052
1091
|
"content:after:create",
|
|
@@ -1109,8 +1148,8 @@ apiContentCrudRoutes.put("/:id", requireAuth(), requireRole(["admin", "editor",
|
|
|
1109
1148
|
await svc.unpublish(pub.id);
|
|
1110
1149
|
}
|
|
1111
1150
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1112
|
-
await cache.invalidate("content-filtered:*");
|
|
1113
|
-
await cache.invalidate(
|
|
1151
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1152
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
1114
1153
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
1115
1154
|
const saved = await db.prepare("SELECT * FROM documents WHERE id = ?").bind(newDraft.id).first();
|
|
1116
1155
|
const savedData = saved?.data ? JSON.parse(saved.data) : {};
|
|
@@ -1149,8 +1188,8 @@ apiContentCrudRoutes.delete("/:id", requireAuth(), requireRole(["admin", "editor
|
|
|
1149
1188
|
const now = Math.floor(Date.now() / 1e3);
|
|
1150
1189
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = ?").bind(now, now, id, tenantId).run();
|
|
1151
1190
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1152
|
-
await cache.invalidate("content-filtered:*");
|
|
1153
|
-
await cache.invalidate(
|
|
1191
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1192
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
1154
1193
|
dispatchHookEvent(c, "content:after:delete", { collection: docRow.type_id, id, data: {}, user: actor }, "fire-and-forget");
|
|
1155
1194
|
return c.json({ success: true });
|
|
1156
1195
|
}
|
|
@@ -1166,6 +1205,22 @@ apiContentCrudRoutes.delete("/:id", requireAuth(), requireRole(["admin", "editor
|
|
|
1166
1205
|
var api_content_crud_default = apiContentCrudRoutes;
|
|
1167
1206
|
|
|
1168
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
|
+
}
|
|
1169
1224
|
var DOC_SORTABLE_COLUMNS = /* @__PURE__ */ new Set([
|
|
1170
1225
|
"created_at",
|
|
1171
1226
|
"updated_at",
|
|
@@ -1249,6 +1304,33 @@ function mapDocRowToContent(row, collectionId) {
|
|
|
1249
1304
|
updated_at: documentSecondsToMs(row.updated_at)
|
|
1250
1305
|
};
|
|
1251
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
|
+
}
|
|
1252
1334
|
var apiRoutes = new Hono();
|
|
1253
1335
|
apiRoutes.use("*", async (c, next) => {
|
|
1254
1336
|
const startTime = Date.now();
|
|
@@ -1738,7 +1820,12 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1738
1820
|
try {
|
|
1739
1821
|
const db = c.env.DB;
|
|
1740
1822
|
const queryParams = c.req.query();
|
|
1823
|
+
const projFields = parseFieldsParam(queryParams.fields);
|
|
1824
|
+
if (queryParams.fields) delete queryParams.fields;
|
|
1741
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);
|
|
1742
1829
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1743
1830
|
let typeId;
|
|
1744
1831
|
let typeIds;
|
|
@@ -1784,7 +1871,7 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1784
1871
|
details: queryResult.errors
|
|
1785
1872
|
}, 400);
|
|
1786
1873
|
}
|
|
1787
|
-
const cacheEnabled = c.get("cacheEnabled");
|
|
1874
|
+
const cacheEnabled = c.get("cacheEnabled") && cAnon;
|
|
1788
1875
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1789
1876
|
const cacheKey = cache.generateKey("content-filtered", JSON.stringify({ filter: normalizedFilter, query: queryResult.sql }));
|
|
1790
1877
|
if (cacheEnabled) {
|
|
@@ -1814,11 +1901,41 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1814
1901
|
const stmt = db.prepare(queryResult.sql);
|
|
1815
1902
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1816
1903
|
const { results } = await boundStmt.all();
|
|
1817
|
-
|
|
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));
|
|
1818
1935
|
const responseData = {
|
|
1819
|
-
data: transformedResults,
|
|
1936
|
+
data: projFields.length ? transformedResults.map((item) => projectFields(item, projFields)) : transformedResults,
|
|
1820
1937
|
meta: addTimingMeta(c, {
|
|
1821
|
-
count:
|
|
1938
|
+
count: aclResults.length,
|
|
1822
1939
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1823
1940
|
// D44: echo the caller's filter with the access policy applied (status=published forced for
|
|
1824
1941
|
// anonymous callers — the visible enforcement proof), NOT the internal doc-augmented where-tree.
|
|
@@ -1829,7 +1946,7 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1829
1946
|
}
|
|
1830
1947
|
}, executionStart)
|
|
1831
1948
|
};
|
|
1832
|
-
if (cacheEnabled) {
|
|
1949
|
+
if (cacheEnabled && projFields.length === 0) {
|
|
1833
1950
|
await cache.set(cacheKey, responseData);
|
|
1834
1951
|
}
|
|
1835
1952
|
return c.json(responseData);
|
|
@@ -1854,6 +1971,16 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1854
1971
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1855
1972
|
collIdByName.set(collection, record.id);
|
|
1856
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
|
+
}
|
|
1857
1984
|
const filter = QueryFilterBuilder.parseFromQuery(queryParams);
|
|
1858
1985
|
const normalizedFilter = augmentFilterForDocuments(filter, { typeId: collection, role });
|
|
1859
1986
|
if (!normalizedFilter.limit) {
|
|
@@ -1868,7 +1995,7 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1868
1995
|
details: queryResult.errors
|
|
1869
1996
|
}, 400);
|
|
1870
1997
|
}
|
|
1871
|
-
const cacheEnabled = c.get("cacheEnabled");
|
|
1998
|
+
const cacheEnabled = c.get("cacheEnabled") && ccAnon;
|
|
1872
1999
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1873
2000
|
const includeCollection = queryParams.include?.split(",").map((s) => s.trim()).includes("collection");
|
|
1874
2001
|
const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql, includeCollection })}`);
|
|
@@ -1899,12 +2026,24 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1899
2026
|
const stmt = db.prepare(queryResult.sql);
|
|
1900
2027
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1901
2028
|
const { results } = await boundStmt.all();
|
|
1902
|
-
|
|
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));
|
|
1903
2042
|
const responseData = {
|
|
1904
2043
|
data: transformedResults,
|
|
1905
2044
|
meta: addTimingMeta(c, {
|
|
1906
2045
|
...includeCollection ? { collection: collectionRecordToRow(record) } : {},
|
|
1907
|
-
count:
|
|
2046
|
+
count: ccAclResults.length,
|
|
1908
2047
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1909
2048
|
// D44: echo the caller's filter with the access policy applied (status=published forced for
|
|
1910
2049
|
// anonymous callers — the visible enforcement proof), NOT the internal doc-augmented where-tree.
|
|
@@ -1934,6 +2073,8 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1934
2073
|
const collection = c.req.param("collection");
|
|
1935
2074
|
const db = c.env.DB;
|
|
1936
2075
|
const queryParams = c.req.query();
|
|
2076
|
+
const projFields = parseFieldsParam(queryParams.fields);
|
|
2077
|
+
if (queryParams.fields) delete queryParams.fields;
|
|
1937
2078
|
const record = getCollectionRegistry().getBySlugOrName(collection);
|
|
1938
2079
|
if (!record || record.isActive === false) {
|
|
1939
2080
|
return c.json({ error: "Collection not found" }, 404);
|
|
@@ -1941,6 +2082,16 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1941
2082
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1942
2083
|
collIdByName.set(record.name, record.id);
|
|
1943
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
|
+
}
|
|
1944
2095
|
const filter = QueryFilterBuilder.parseFromQuery(queryParams);
|
|
1945
2096
|
const normalizedFilter = augmentFilterForDocuments(filter, { typeId: record.name, role });
|
|
1946
2097
|
if (!normalizedFilter.limit) normalizedFilter.limit = 50;
|
|
@@ -1952,7 +2103,7 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1952
2103
|
}
|
|
1953
2104
|
const collectionCache = record.cache;
|
|
1954
2105
|
const collectionCacheDisabled = collectionCache?.enabled === false;
|
|
1955
|
-
const cacheEnabled = c.get("cacheEnabled") && !collectionCacheDisabled;
|
|
2106
|
+
const cacheEnabled = c.get("cacheEnabled") && !collectionCacheDisabled && projFields.length === 0 && anon;
|
|
1956
2107
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1957
2108
|
const includeCollection = queryParams.include?.split(",").map((s) => s.trim()).includes("collection");
|
|
1958
2109
|
const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql, includeCollection })}`);
|
|
@@ -1970,12 +2121,25 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1970
2121
|
const stmt = db.prepare(queryResult.sql);
|
|
1971
2122
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1972
2123
|
const { results } = await boundStmt.all();
|
|
1973
|
-
|
|
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));
|
|
1974
2138
|
const responseData = {
|
|
1975
|
-
data: transformedResults,
|
|
2139
|
+
data: projFields.length ? transformedResults.map((item) => projectFields(item, projFields)) : transformedResults,
|
|
1976
2140
|
meta: addTimingMeta(c, {
|
|
1977
2141
|
...includeCollection ? { collection: collectionRecordToRow(record) } : {},
|
|
1978
|
-
count:
|
|
2142
|
+
count: aclResults.length,
|
|
1979
2143
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1980
2144
|
filter: normalizePublicContentFilter(filter, role),
|
|
1981
2145
|
cache: { hit: false, source: "database" }
|
|
@@ -2006,6 +2170,23 @@ apiRoutes.get("/:collection/:id", optionalAuth(), async (c) => {
|
|
|
2006
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"
|
|
2007
2171
|
).bind(id, record.name).first();
|
|
2008
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"));
|
|
2009
2190
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
2010
2191
|
const transformedContent = {
|
|
2011
2192
|
id: docRow.root_id,
|
|
@@ -2018,7 +2199,7 @@ apiRoutes.get("/:collection/:id", optionalAuth(), async (c) => {
|
|
|
2018
2199
|
updated_at: documentSecondsToMs(docRow.updated_at)
|
|
2019
2200
|
};
|
|
2020
2201
|
dispatchHookEvent(c, "content:read", { collection: docRow.type_id, id: docRow.root_id, data: transformedContent.data }, "fire-and-forget");
|
|
2021
|
-
return c.json({ data: transformedContent });
|
|
2202
|
+
return c.json({ data: projFields.length ? projectFields(transformedContent, projFields) : transformedContent });
|
|
2022
2203
|
} catch (error) {
|
|
2023
2204
|
console.error("Error fetching content:", error);
|
|
2024
2205
|
return c.json({ error: "Failed to fetch content", details: error instanceof Error ? error.message : String(error) }, 500);
|
|
@@ -2048,8 +2229,8 @@ apiRoutes.post("/:collection", requireAuth(), requireRole(["admin", "editor", "a
|
|
|
2048
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 });
|
|
2049
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);
|
|
2050
2231
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2051
|
-
await cache.invalidate("content-filtered:*");
|
|
2052
|
-
await cache.invalidate(
|
|
2232
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2233
|
+
await cache.invalidate(`api:collection-content-filtered:${backing.coll.name}:*`);
|
|
2053
2234
|
dispatchHookEvent(c, "content:after:create", { collection: backing.coll.name, id: doc.rootId, data: doc.data ?? {}, user: actor }, "fire-and-forget");
|
|
2054
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);
|
|
2055
2236
|
} catch (error) {
|
|
@@ -2091,8 +2272,8 @@ apiRoutes.put("/:collection/:id", requireAuth(), requireRole(["admin", "editor",
|
|
|
2091
2272
|
await svc.unpublish(pub.id);
|
|
2092
2273
|
}
|
|
2093
2274
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2094
|
-
await cache.invalidate("content-filtered:*");
|
|
2095
|
-
await cache.invalidate(
|
|
2275
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2276
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
2096
2277
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
2097
2278
|
const saved = await db.prepare("SELECT * FROM documents WHERE id = ?").bind(newDraft.id).first();
|
|
2098
2279
|
const savedData = saved?.data ? JSON.parse(saved.data) : {};
|
|
@@ -2124,8 +2305,8 @@ apiRoutes.delete("/:collection/:id", requireAuth(), requireRole(["admin", "edito
|
|
|
2124
2305
|
const now = Math.floor(Date.now() / 1e3);
|
|
2125
2306
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = 'default'").bind(now, now, id).run();
|
|
2126
2307
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2127
|
-
await cache.invalidate("content-filtered:*");
|
|
2128
|
-
await cache.invalidate(
|
|
2308
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2309
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
2129
2310
|
dispatchHookEvent(c, "content:after:delete", { collection: docRow.type_id, id, data: {}, user: actor }, "fire-and-forget");
|
|
2130
2311
|
return c.json({ success: true });
|
|
2131
2312
|
} catch (error) {
|
|
@@ -2687,9 +2868,13 @@ apiMediaRoutes.post("/create-folder", async (c) => {
|
|
|
2687
2868
|
error: "Folder name can only contain lowercase letters, numbers, hyphens, and underscores"
|
|
2688
2869
|
}, 400);
|
|
2689
2870
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
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) {
|
|
2693
2878
|
return c.json({
|
|
2694
2879
|
success: false,
|
|
2695
2880
|
error: `Folder "${folderName}" already exists`
|
|
@@ -3399,7 +3584,7 @@ adminApiRoutes.patch("/collections/:id", collectionsReadOnly);
|
|
|
3399
3584
|
adminApiRoutes.delete("/collections/:id", collectionsReadOnly);
|
|
3400
3585
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
3401
3586
|
try {
|
|
3402
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
3587
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-JAHMI5MK.js');
|
|
3403
3588
|
const db = c.env.DB;
|
|
3404
3589
|
const migrationService = new MigrationService2(db);
|
|
3405
3590
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -3442,7 +3627,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
3442
3627
|
});
|
|
3443
3628
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
3444
3629
|
try {
|
|
3445
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
3630
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-JAHMI5MK.js');
|
|
3446
3631
|
const db = c.env.DB;
|
|
3447
3632
|
const migrationService = new MigrationService2(db);
|
|
3448
3633
|
const validation = await migrationService.validateSchema();
|
|
@@ -3601,6 +3786,8 @@ function renderLoginPage(data, demoLoginActive = false) {
|
|
|
3601
3786
|
<!-- Form -->
|
|
3602
3787
|
<form
|
|
3603
3788
|
id="login-form"
|
|
3789
|
+
action="/auth/login/form${data.redirect ? `?redirect=${encodeURIComponent(data.redirect)}` : ""}"
|
|
3790
|
+
method="post"
|
|
3604
3791
|
hx-post="/auth/login/form${data.redirect ? `?redirect=${encodeURIComponent(data.redirect)}` : ""}"
|
|
3605
3792
|
hx-target="#form-response"
|
|
3606
3793
|
hx-swap="innerHTML"
|
|
@@ -5116,9 +5303,11 @@ function renderDynamicField(field, options = {}) {
|
|
|
5116
5303
|
min="${opts.min || ""}"
|
|
5117
5304
|
max="${opts.max || ""}"
|
|
5118
5305
|
class="${baseClasses} ${errorClasses} !w-auto dark:[color-scheme:dark]"
|
|
5306
|
+
placeholder="YYYY-MM-DD"
|
|
5119
5307
|
${required}
|
|
5120
5308
|
${disabled ? "disabled" : ""}
|
|
5121
5309
|
>
|
|
5310
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Format: YYYY-MM-DD</p>
|
|
5122
5311
|
`;
|
|
5123
5312
|
break;
|
|
5124
5313
|
case "datetime":
|
|
@@ -5134,6 +5323,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5134
5323
|
${required}
|
|
5135
5324
|
${disabled ? "disabled" : ""}
|
|
5136
5325
|
>
|
|
5326
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Format: YYYY-MM-DD HH:MM</p>
|
|
5137
5327
|
`;
|
|
5138
5328
|
break;
|
|
5139
5329
|
case "slug":
|
|
@@ -7090,6 +7280,21 @@ function createUserProfilesPlugin() {
|
|
|
7090
7280
|
|
|
7091
7281
|
// src/routes/auth.ts
|
|
7092
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
|
+
}
|
|
7093
7298
|
function renderPasswordResetEmail(resetLink, firstName) {
|
|
7094
7299
|
const greeting = firstName ? `Hi ${firstName},` : "Hello,";
|
|
7095
7300
|
return `<!DOCTYPE html>
|
|
@@ -7281,8 +7486,8 @@ authRoutes.post(
|
|
|
7281
7486
|
}
|
|
7282
7487
|
const { email, password } = validation.data;
|
|
7283
7488
|
const normalizedEmail = email.toLowerCase();
|
|
7284
|
-
const { createAuth } = await import('./config-
|
|
7285
|
-
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);
|
|
7286
7491
|
const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
|
|
7287
7492
|
method: "POST",
|
|
7288
7493
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin },
|
|
@@ -7292,12 +7497,14 @@ authRoutes.post(
|
|
|
7292
7497
|
if (!baRes.ok) {
|
|
7293
7498
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
7294
7499
|
}
|
|
7295
|
-
|
|
7296
|
-
if (rawSetCookie) {
|
|
7297
|
-
c.res.headers.append("Set-Cookie", rawSetCookie);
|
|
7298
|
-
} else if (baRes.headers.getSetCookie) {
|
|
7500
|
+
if (baRes.headers.getSetCookie) {
|
|
7299
7501
|
for (const sc of baRes.headers.getSetCookie()) {
|
|
7300
|
-
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 });
|
|
7301
7508
|
}
|
|
7302
7509
|
}
|
|
7303
7510
|
await setCsrfCookie(c);
|
|
@@ -7317,6 +7524,9 @@ authRoutes.post(
|
|
|
7317
7524
|
sameSite: "Strict",
|
|
7318
7525
|
maxAge: tokenTtl
|
|
7319
7526
|
});
|
|
7527
|
+
if (normalizedEmail === DEMO_EMAIL) {
|
|
7528
|
+
c.executionCtx?.waitUntil(trackDemoLogin(c.env.CACHE_KV));
|
|
7529
|
+
}
|
|
7320
7530
|
return c.json({
|
|
7321
7531
|
user: {
|
|
7322
7532
|
id: user.id,
|
|
@@ -7335,8 +7545,8 @@ authRoutes.post(
|
|
|
7335
7545
|
);
|
|
7336
7546
|
authRoutes.post("/logout", async (c) => {
|
|
7337
7547
|
try {
|
|
7338
|
-
const { createAuth } = await import('./config-
|
|
7339
|
-
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);
|
|
7340
7550
|
const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
|
|
7341
7551
|
method: "POST",
|
|
7342
7552
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin, "Cookie": c.req.header("Cookie") || "" },
|
|
@@ -7345,14 +7555,15 @@ authRoutes.post("/logout", async (c) => {
|
|
|
7345
7555
|
await auth.handler(baReq);
|
|
7346
7556
|
} catch {
|
|
7347
7557
|
}
|
|
7348
|
-
|
|
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 });
|
|
7349
7560
|
clearCsrfCookie(c);
|
|
7350
7561
|
return c.json({ message: "Logged out successfully" });
|
|
7351
7562
|
});
|
|
7352
7563
|
authRoutes.get("/logout", async (c) => {
|
|
7353
7564
|
try {
|
|
7354
|
-
const { createAuth } = await import('./config-
|
|
7355
|
-
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);
|
|
7356
7567
|
const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
|
|
7357
7568
|
method: "POST",
|
|
7358
7569
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin, "Cookie": c.req.header("Cookie") || "" },
|
|
@@ -7361,7 +7572,8 @@ authRoutes.get("/logout", async (c) => {
|
|
|
7361
7572
|
await auth.handler(baReq);
|
|
7362
7573
|
} catch {
|
|
7363
7574
|
}
|
|
7364
|
-
|
|
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 });
|
|
7365
7577
|
clearCsrfCookie(c);
|
|
7366
7578
|
return c.redirect("/auth/login?message=You have been logged out successfully");
|
|
7367
7579
|
});
|
|
@@ -7558,8 +7770,8 @@ authRoutes.post(
|
|
|
7558
7770
|
</div>
|
|
7559
7771
|
`);
|
|
7560
7772
|
}
|
|
7561
|
-
const { createAuth } = await import('./config-
|
|
7562
|
-
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);
|
|
7563
7775
|
const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
|
|
7564
7776
|
method: "POST",
|
|
7565
7777
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin },
|
|
@@ -7573,17 +7785,27 @@ authRoutes.post(
|
|
|
7573
7785
|
</div>
|
|
7574
7786
|
`);
|
|
7575
7787
|
}
|
|
7576
|
-
|
|
7577
|
-
if (rawSetCookie) {
|
|
7578
|
-
c.res.headers.append("Set-Cookie", rawSetCookie);
|
|
7579
|
-
} else if (baRes.headers.getSetCookie) {
|
|
7788
|
+
if (baRes.headers.getSetCookie) {
|
|
7580
7789
|
for (const sc of baRes.headers.getSetCookie()) {
|
|
7581
|
-
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 });
|
|
7582
7796
|
}
|
|
7583
7797
|
}
|
|
7584
7798
|
await setCsrfCookie(c);
|
|
7799
|
+
if (email === DEMO_EMAIL) {
|
|
7800
|
+
c.executionCtx?.waitUntil(trackDemoLogin(c.env.CACHE_KV));
|
|
7801
|
+
}
|
|
7585
7802
|
const rawRedirect = c.req.query("redirect");
|
|
7586
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
|
+
}
|
|
7587
7809
|
return c.html(html`
|
|
7588
7810
|
<div id="form-response">
|
|
7589
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">
|
|
@@ -9008,20 +9230,20 @@ function renderContentFormPage(data, opts) {
|
|
|
9008
9230
|
<dl class="space-y-3 text-sm">
|
|
9009
9231
|
<div>
|
|
9010
9232
|
<dt class="text-zinc-500 dark:text-zinc-400">Created</dt>
|
|
9011
|
-
<dd class="mt-1 text-zinc-950 dark:text-white">${data.created_at ? new Date(data.created_at).toLocaleDateString() : "Unknown"}</dd>
|
|
9233
|
+
<dd class="mt-1 text-zinc-950 dark:text-white">${data.created_at ? new Date(data.created_at * 1e3).toLocaleDateString() : "Unknown"}</dd>
|
|
9012
9234
|
</div>
|
|
9013
9235
|
<div>
|
|
9014
9236
|
<dt class="text-zinc-500 dark:text-zinc-400">Last Modified</dt>
|
|
9015
|
-
<dd class="mt-1 text-zinc-950 dark:text-white">${data.updated_at ? new Date(data.updated_at).toLocaleDateString() : "Unknown"}</dd>
|
|
9237
|
+
<dd class="mt-1 text-zinc-950 dark:text-white">${data.updated_at ? new Date(data.updated_at * 1e3).toLocaleDateString() : "Unknown"}</dd>
|
|
9016
9238
|
</div>
|
|
9017
9239
|
<div>
|
|
9018
9240
|
<dt class="text-zinc-500 dark:text-zinc-400">Author</dt>
|
|
9019
|
-
<dd class="mt-1 text-zinc-950 dark:text-white">${data.data?.author || "Unknown"}</dd>
|
|
9241
|
+
<dd class="mt-1 text-zinc-950 dark:text-white">${escapeHtml(String(data.data?.author || data.author_name || "Unknown"))}</dd>
|
|
9020
9242
|
</div>
|
|
9021
9243
|
${data.published_at ? `
|
|
9022
9244
|
<div>
|
|
9023
9245
|
<dt class="text-zinc-500 dark:text-zinc-400">Published</dt>
|
|
9024
|
-
<dd class="mt-1 text-zinc-950 dark:text-white">${new Date(data.published_at).toLocaleDateString()}</dd>
|
|
9246
|
+
<dd class="mt-1 text-zinc-950 dark:text-white">${new Date(data.published_at * 1e3).toLocaleDateString()}</dd>
|
|
9025
9247
|
</div>
|
|
9026
9248
|
` : ""}
|
|
9027
9249
|
</dl>
|
|
@@ -9095,33 +9317,44 @@ function renderContentFormPage(data, opts) {
|
|
|
9095
9317
|
</a>
|
|
9096
9318
|
|
|
9097
9319
|
<div class="flex items-center gap-x-3">
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
>
|
|
9106
|
-
<
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
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>
|
|
9332
|
+
|
|
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>
|
|
9112
9345
|
|
|
9113
|
-
${data.user?.role !== "viewer" ? `
|
|
9346
|
+
${data.user?.role !== "viewer" && data.versioningEnabled ? `
|
|
9114
9347
|
<button
|
|
9115
9348
|
type="submit"
|
|
9116
9349
|
form="content-form"
|
|
9117
9350
|
name="action"
|
|
9118
9351
|
value="save_and_publish"
|
|
9119
|
-
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"
|
|
9120
9353
|
>
|
|
9121
9354
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
9122
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"/>
|
|
9123
9356
|
</svg>
|
|
9124
|
-
|
|
9357
|
+
Publish
|
|
9125
9358
|
</button>
|
|
9126
9359
|
` : ""}
|
|
9127
9360
|
</div>
|
|
@@ -11623,6 +11856,14 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
11623
11856
|
docSql += " ORDER BY updated_at DESC LIMIT ? OFFSET ?";
|
|
11624
11857
|
docParams.push(limit, offset);
|
|
11625
11858
|
const { results: docRows } = await db.prepare(docSql).bind(...docParams).all();
|
|
11859
|
+
const docUserIds = [...new Set((docRows ?? []).map((r) => r.created_by).filter(Boolean))];
|
|
11860
|
+
const docAuthorMap = {};
|
|
11861
|
+
for (let i = 0; i < docUserIds.length; i += 99) {
|
|
11862
|
+
const chunk = docUserIds.slice(i, i + 99);
|
|
11863
|
+
const ph = chunk.map(() => "?").join(",");
|
|
11864
|
+
const { results: uRows } = await db.prepare(`SELECT id, email FROM auth_user WHERE id IN (${ph})`).bind(...chunk).all();
|
|
11865
|
+
for (const u of uRows ?? []) docAuthorMap[u.id] = u.email;
|
|
11866
|
+
}
|
|
11626
11867
|
const statusBadgeCss = {
|
|
11627
11868
|
published: "bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400 ring-1 ring-inset ring-green-600/20 dark:ring-green-500/20",
|
|
11628
11869
|
draft: "bg-zinc-50 dark:bg-zinc-500/10 text-zinc-700 dark:text-zinc-400 ring-1 ring-inset ring-zinc-600/20 dark:ring-zinc-500/20"
|
|
@@ -11639,7 +11880,7 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
11639
11880
|
slug: row.slug || "",
|
|
11640
11881
|
modelName: docType?.displayName ?? typeId,
|
|
11641
11882
|
statusBadge: `<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ${css}">${label}</span>`,
|
|
11642
|
-
authorName: row.created_by || "System",
|
|
11883
|
+
authorName: (row.created_by ? docAuthorMap[row.created_by] ?? row.created_by : null) || "System",
|
|
11643
11884
|
// Document timestamps are stored in SECONDS (documents.ts), unlike legacy content rows which
|
|
11644
11885
|
// store MILLISECONDS — hence the *1000 here (D23).
|
|
11645
11886
|
formattedDate: new Date((row.updated_at ?? 0) * 1e3).toLocaleDateString(),
|
|
@@ -11687,8 +11928,8 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
11687
11928
|
SELECT d.root_id AS id, d.title AS title, d.slug AS slug,
|
|
11688
11929
|
CASE WHEN d.is_published = 1 THEN 'published' ELSE 'draft' END AS status,
|
|
11689
11930
|
d.updated_at * 1000 AS updated_at,
|
|
11690
|
-
dt.display_name AS cdisplay, COALESCE(d.created_by, 'System') AS author_label
|
|
11691
|
-
FROM documents d JOIN document_types dt ON dt.id = d.type_id
|
|
11931
|
+
dt.display_name AS cdisplay, COALESCE(u.email, d.created_by, 'System') AS author_label
|
|
11932
|
+
FROM documents d JOIN document_types dt ON dt.id = d.type_id LEFT JOIN auth_user u ON u.id = d.created_by
|
|
11692
11933
|
WHERE ${docConds.join(" AND ")}
|
|
11693
11934
|
ORDER BY updated_at DESC LIMIT ? OFFSET ?`;
|
|
11694
11935
|
const { results: unionRows } = await db.prepare(unionSql).bind(...docParams, limit, offset).all();
|
|
@@ -12029,6 +12270,13 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
12029
12270
|
if (docType && dcoll) {
|
|
12030
12271
|
const fields2 = await getCollectionFields(db, dcoll.id);
|
|
12031
12272
|
const flags = await loadContentEditorFlags(db);
|
|
12273
|
+
let authorName;
|
|
12274
|
+
if (docRow.created_by) {
|
|
12275
|
+
const authorRow = await db.prepare("SELECT name, first_name, last_name, email FROM auth_user WHERE id = ?").bind(docRow.created_by).first();
|
|
12276
|
+
if (authorRow) {
|
|
12277
|
+
authorName = authorRow.name || [authorRow.first_name, authorRow.last_name].filter(Boolean).join(" ") || authorRow.email;
|
|
12278
|
+
}
|
|
12279
|
+
}
|
|
12032
12280
|
const formData2 = {
|
|
12033
12281
|
id: docRow.root_id,
|
|
12034
12282
|
title: docRow.title,
|
|
@@ -12043,6 +12291,7 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
12043
12291
|
isEdit: true,
|
|
12044
12292
|
referrerParams,
|
|
12045
12293
|
versioningEnabled: docType?.settings?.versioning === true,
|
|
12294
|
+
author_name: authorName,
|
|
12046
12295
|
user: user ? { name: user.email, email: user.email, role: user.role } : void 0,
|
|
12047
12296
|
version: c.get("appVersion"),
|
|
12048
12297
|
...flags
|
|
@@ -12202,6 +12451,7 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
12202
12451
|
const fields = await getCollectionFields(db, collectionId);
|
|
12203
12452
|
const { data, errors } = extractFieldData(fields, formData);
|
|
12204
12453
|
if (Object.keys(errors).length > 0) {
|
|
12454
|
+
const flags = await loadContentEditorFlags(db);
|
|
12205
12455
|
const formDataWithErrors = {
|
|
12206
12456
|
collection,
|
|
12207
12457
|
fields,
|
|
@@ -12212,7 +12462,8 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
12212
12462
|
name: user.email,
|
|
12213
12463
|
email: user.email,
|
|
12214
12464
|
role: user.role
|
|
12215
|
-
} : void 0
|
|
12465
|
+
} : void 0,
|
|
12466
|
+
...flags
|
|
12216
12467
|
};
|
|
12217
12468
|
if (c.req.header("HX-Request") === "true") {
|
|
12218
12469
|
c.header("HX-Retarget", "#content-form-page");
|
|
@@ -12246,6 +12497,9 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
12246
12497
|
}), user?.userId);
|
|
12247
12498
|
const cache2 = getCacheService(CACHE_CONFIGS.content);
|
|
12248
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}:*`);
|
|
12249
12503
|
const redirectUrl2 = `/admin/content/${doc.rootId}/edit?success=Content created successfully!`;
|
|
12250
12504
|
return c.req.header("HX-Request") === "true" ? c.text("", 200, { "HX-Redirect": redirectUrl2 }) : c.redirect(redirectUrl2);
|
|
12251
12505
|
}
|
|
@@ -12364,8 +12618,28 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
12364
12618
|
if (status2 === "published") await svc.publish(newDraft.id, user?.userId);
|
|
12365
12619
|
else if (pub) await svc.unpublish(pub.id);
|
|
12366
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
|
+
}
|
|
12367
12641
|
const redirectUrl2 = `/admin/content/${id}/edit?success=Content updated successfully!`;
|
|
12368
|
-
return c.
|
|
12642
|
+
return c.redirect(redirectUrl2);
|
|
12369
12643
|
}
|
|
12370
12644
|
}
|
|
12371
12645
|
return c.html(html`
|
|
@@ -12393,6 +12667,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
12393
12667
|
const fields = await getCollectionFields(db, existingContent.collection_id);
|
|
12394
12668
|
const { data, errors } = extractFieldData(fields, formData);
|
|
12395
12669
|
if (Object.keys(errors).length > 0) {
|
|
12670
|
+
const flags = await loadContentEditorFlags(db);
|
|
12396
12671
|
const formDataWithErrors = {
|
|
12397
12672
|
id,
|
|
12398
12673
|
collection,
|
|
@@ -12405,7 +12680,8 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
12405
12680
|
name: user.email,
|
|
12406
12681
|
email: user.email,
|
|
12407
12682
|
role: user.role
|
|
12408
|
-
} : void 0
|
|
12683
|
+
} : void 0,
|
|
12684
|
+
...flags
|
|
12409
12685
|
};
|
|
12410
12686
|
if (c.req.header("HX-Request") === "true") {
|
|
12411
12687
|
c.header("HX-Retarget", "#content-form-page");
|
|
@@ -12757,8 +13033,8 @@ adminContentRoutes.post("/bulk-action", async (c) => {
|
|
|
12757
13033
|
}
|
|
12758
13034
|
await cache.invalidate("content:list:*");
|
|
12759
13035
|
const apiCache = getCacheService(CACHE_CONFIGS.api);
|
|
12760
|
-
await apiCache.invalidate("content-filtered:*");
|
|
12761
|
-
await apiCache.invalidate("collection-content-filtered:*");
|
|
13036
|
+
await apiCache.invalidate("api:content-filtered:*");
|
|
13037
|
+
await apiCache.invalidate("api:collection-content-filtered:*");
|
|
12762
13038
|
return c.json({ success: true, count: ids.length });
|
|
12763
13039
|
} catch (error) {
|
|
12764
13040
|
console.error("Bulk action error:", error);
|
|
@@ -12776,6 +13052,9 @@ adminContentRoutes.delete("/:id", async (c) => {
|
|
|
12776
13052
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
12777
13053
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = ?").bind(now2, now2, id, tenantId).run();
|
|
12778
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}:*`);
|
|
12779
13058
|
return c.html(`
|
|
12780
13059
|
<div id="content-list" hx-get="/admin/content?model=${docDel.type_id}" hx-trigger="load" hx-swap="outerHTML">
|
|
12781
13060
|
<div class="flex items-center justify-center p-8"><span class="text-zinc-500">Deleting\u2026</span></div>
|
|
@@ -13071,12 +13350,12 @@ adminContentRoutes.post("/documents/:typeId/:documentId/delete", async (c) => {
|
|
|
13071
13350
|
adminContentRoutes.get("/documents/:typeId/:rootId/versions", async (c) => {
|
|
13072
13351
|
const { typeId, rootId } = c.req.param();
|
|
13073
13352
|
const db = c.env.DB;
|
|
13074
|
-
const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-
|
|
13353
|
+
const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-JWV5GITD.js');
|
|
13075
13354
|
const registry = new DocumentTypeRegistry(db);
|
|
13076
13355
|
const docType = await registry.findById(typeId);
|
|
13077
13356
|
if (!docType) return c.html("<div>Unknown type.</div>", 404);
|
|
13078
13357
|
const result = await db.prepare(
|
|
13079
|
-
"SELECT id, version_number, is_current_draft, is_published, status, updated_at, created_by FROM documents WHERE root_id = ? AND tenant_id = ? ORDER BY version_number DESC LIMIT 50"
|
|
13358
|
+
"SELECT d.id, d.version_number, d.is_current_draft, d.is_published, d.status, d.updated_at, COALESCE(u.email, d.created_by) AS created_by FROM documents d LEFT JOIN auth_user u ON u.id = d.created_by WHERE d.root_id = ? AND d.tenant_id = ? ORDER BY d.version_number DESC LIMIT 50"
|
|
13080
13359
|
).bind(rootId, reqTenant(c)).all();
|
|
13081
13360
|
const versions = (result.results ?? []).map((r) => ({
|
|
13082
13361
|
id: r.id,
|
|
@@ -15326,12 +15605,10 @@ var LANGUAGES = [
|
|
|
15326
15605
|
{ value: "ko", label: "Korean" },
|
|
15327
15606
|
{ value: "zh", label: "Chinese" }
|
|
15328
15607
|
];
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
{ value:
|
|
15332
|
-
|
|
15333
|
-
{ value: "viewer", label: "Viewer" }
|
|
15334
|
-
];
|
|
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
|
+
}
|
|
15335
15612
|
userRoutes.get("/profile", async (c) => {
|
|
15336
15613
|
const user = c.get("user");
|
|
15337
15614
|
const db = c.env.DB;
|
|
@@ -15780,8 +16057,9 @@ userRoutes.get("/users/new", async (c) => {
|
|
|
15780
16057
|
const regFieldNames = profileConfig?.registrationFields ?? [];
|
|
15781
16058
|
const regFields = profileConfig && upRow ? regFieldNames.length > 0 ? profileConfig.fields.filter((f) => regFieldNames.includes(f.name)) : profileConfig.fields : [];
|
|
15782
16059
|
const registrationFieldsHtml = regFields.length > 0 ? renderCustomProfileSection({ fields: regFields }, {}) : void 0;
|
|
16060
|
+
const roles = await fetchRoleOptions(db);
|
|
15783
16061
|
const pageData = {
|
|
15784
|
-
roles
|
|
16062
|
+
roles,
|
|
15785
16063
|
registrationFieldsHtml,
|
|
15786
16064
|
user: {
|
|
15787
16065
|
name: user.email.split("@")[0] || user.email,
|
|
@@ -15809,9 +16087,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
15809
16087
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
15810
16088
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
15811
16089
|
const bio = sanitizeInput(formData.get("bio")?.toString()) || null;
|
|
15812
|
-
const roleInput = formData.get("role")?.toString() || "
|
|
15813
|
-
const validRoles =
|
|
15814
|
-
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";
|
|
15815
16093
|
const password = formData.get("password")?.toString() || "";
|
|
15816
16094
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
15817
16095
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -16033,9 +16311,10 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
16033
16311
|
}
|
|
16034
16312
|
const queryMessage = c.req.query("message");
|
|
16035
16313
|
const queryType = c.req.query("type") === "error" ? "error" : "success";
|
|
16314
|
+
const roles = await fetchRoleOptions(db);
|
|
16036
16315
|
const pageData = {
|
|
16037
16316
|
userToEdit: editData,
|
|
16038
|
-
roles
|
|
16317
|
+
roles,
|
|
16039
16318
|
hasProfilePlugin: profileConfig !== null && upRow !== null,
|
|
16040
16319
|
customProfileFieldsHtml,
|
|
16041
16320
|
tenantMemberships,
|
|
@@ -16067,9 +16346,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
16067
16346
|
const lastName = sanitizeInput(formData.get("last_name")?.toString());
|
|
16068
16347
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
16069
16348
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
16070
|
-
const roleInput = formData.get("role")?.toString() || "
|
|
16071
|
-
const validRoles =
|
|
16072
|
-
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";
|
|
16073
16352
|
const isActive = formData.get("is_active") === "1";
|
|
16074
16353
|
const emailVerified = formData.get("email_verified") === "1";
|
|
16075
16354
|
const newPassword = formData.get("new_password")?.toString() || "";
|
|
@@ -18656,15 +18935,6 @@ function renderPluginsListPage(data) {
|
|
|
18656
18935
|
</div>
|
|
18657
18936
|
|
|
18658
18937
|
<div class="flex items-center gap-3 w-full sm:w-auto">
|
|
18659
|
-
<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">
|
|
18660
|
-
<option value="name-asc">Name (A-Z)</option>
|
|
18661
|
-
<option value="name-desc">Name (Z-A)</option>
|
|
18662
|
-
<option value="newest">Newest Installed</option>
|
|
18663
|
-
<option value="updated">Recently Updated</option>
|
|
18664
|
-
<option value="popular">Popularity</option>
|
|
18665
|
-
<option value="rating">Highest Rated</option>
|
|
18666
|
-
</select>
|
|
18667
|
-
|
|
18668
18938
|
<button
|
|
18669
18939
|
onclick="location.reload()"
|
|
18670
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"
|
|
@@ -18828,8 +19098,6 @@ function renderPluginsListPage(data) {
|
|
|
18828
19098
|
.forEach(cb => params.append('status', cb.value));
|
|
18829
19099
|
const search = document.getElementById('search-input').value;
|
|
18830
19100
|
if (search) params.set('search', search);
|
|
18831
|
-
const sort = document.getElementById('sort-filter').value;
|
|
18832
|
-
if (sort && sort !== 'name-asc') params.set('sort', sort);
|
|
18833
19101
|
const url = new URL(window.location.href);
|
|
18834
19102
|
url.search = params.toString();
|
|
18835
19103
|
history.replaceState(null, '', url.toString());
|
|
@@ -18847,8 +19115,6 @@ function renderPluginsListPage(data) {
|
|
|
18847
19115
|
});
|
|
18848
19116
|
const search = params.get('search');
|
|
18849
19117
|
if (search) document.getElementById('search-input').value = search;
|
|
18850
|
-
const sort = params.get('sort');
|
|
18851
|
-
if (sort) document.getElementById('sort-filter').value = sort;
|
|
18852
19118
|
if (params.toString()) filterAndSortPlugins();
|
|
18853
19119
|
}
|
|
18854
19120
|
|
|
@@ -18864,7 +19130,6 @@ function renderPluginsListPage(data) {
|
|
|
18864
19130
|
.map(cb => cb.value.toLowerCase());
|
|
18865
19131
|
|
|
18866
19132
|
const searchInput = document.getElementById('search-input').value.toLowerCase();
|
|
18867
|
-
const sortValue = document.getElementById('sort-filter').value;
|
|
18868
19133
|
|
|
18869
19134
|
const pluginsGrid = document.getElementById('plugins-grid');
|
|
18870
19135
|
const pluginCards = Array.from(pluginsGrid.querySelectorAll('.plugin-card'));
|
|
@@ -18888,30 +19153,6 @@ function renderPluginsListPage(data) {
|
|
|
18888
19153
|
return true;
|
|
18889
19154
|
});
|
|
18890
19155
|
|
|
18891
|
-
// Sort
|
|
18892
|
-
visibleCards.sort((a, b) => {
|
|
18893
|
-
const aName = a.getAttribute('data-name') || '';
|
|
18894
|
-
const bName = b.getAttribute('data-name') || '';
|
|
18895
|
-
const aInstalled = parseInt(a.getAttribute('data-installed') || '0');
|
|
18896
|
-
const bInstalled = parseInt(b.getAttribute('data-installed') || '0');
|
|
18897
|
-
const aUpdated = parseInt(a.getAttribute('data-updated') || '0');
|
|
18898
|
-
const bUpdated = parseInt(b.getAttribute('data-updated') || '0');
|
|
18899
|
-
const aDownloads = parseInt(a.getAttribute('data-downloads') || '0');
|
|
18900
|
-
const bDownloads = parseInt(b.getAttribute('data-downloads') || '0');
|
|
18901
|
-
const aRating = parseFloat(a.getAttribute('data-rating') || '0');
|
|
18902
|
-
const bRating = parseFloat(b.getAttribute('data-rating') || '0');
|
|
18903
|
-
|
|
18904
|
-
switch (sortValue) {
|
|
18905
|
-
case 'name-desc': return bName.localeCompare(aName);
|
|
18906
|
-
case 'newest': return bInstalled - aInstalled;
|
|
18907
|
-
case 'updated': return bUpdated - aUpdated;
|
|
18908
|
-
case 'popular': return bDownloads - aDownloads;
|
|
18909
|
-
case 'rating': return bRating - aRating;
|
|
18910
|
-
case 'name-asc':
|
|
18911
|
-
default: return aName.localeCompare(bName);
|
|
18912
|
-
}
|
|
18913
|
-
});
|
|
18914
|
-
|
|
18915
19156
|
// Re-append
|
|
18916
19157
|
pluginCards.forEach(card => card.style.display = 'none'); // Hide all first
|
|
18917
19158
|
|
|
@@ -19346,7 +19587,8 @@ function escapeHtmlAttr(value) {
|
|
|
19346
19587
|
function renderPluginSettingsPage(data) {
|
|
19347
19588
|
const { plugin, activity = [], user, settingsTabData } = data;
|
|
19348
19589
|
const pluginDef = getPluginDefinition(plugin.id || plugin.name);
|
|
19349
|
-
const
|
|
19590
|
+
const pluginId = plugin.id || plugin.name;
|
|
19591
|
+
const hasUserSettings = pluginDef?.settingsTabContent != null || Object.keys(plugin.settings || {}).some((k) => !k.startsWith("_")) || hasCustomSettingsComponent(pluginId);
|
|
19350
19592
|
const defaultTab = hasUserSettings ? "settings" : "info";
|
|
19351
19593
|
const pageContent = `
|
|
19352
19594
|
<div class="w-full px-4 sm:px-6 lg:px-8 py-6">
|
|
@@ -19511,9 +19753,28 @@ function renderPluginSettingsPage(data) {
|
|
|
19511
19753
|
const form = document.getElementById('settings-form');
|
|
19512
19754
|
const formData = new FormData(form);
|
|
19513
19755
|
const isAuthPlugin = '${plugin.id}' === 'core-auth';
|
|
19756
|
+
const isOAuthPlugin = '${plugin.id}' === 'oauth-providers';
|
|
19514
19757
|
let settings = {};
|
|
19515
19758
|
|
|
19516
|
-
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) {
|
|
19517
19778
|
// Handle nested auth settings structure
|
|
19518
19779
|
settings = {
|
|
19519
19780
|
requiredFields: {},
|
|
@@ -20001,7 +20262,8 @@ function formatTimestamp(timestamp) {
|
|
|
20001
20262
|
}
|
|
20002
20263
|
var pluginSettingsComponents = {
|
|
20003
20264
|
"otp-login": renderOTPLoginSettingsContent,
|
|
20004
|
-
"email": renderEmailSettingsContent
|
|
20265
|
+
"email": renderEmailSettingsContent,
|
|
20266
|
+
"oauth-providers": renderOAuthProvidersSettingsContent
|
|
20005
20267
|
};
|
|
20006
20268
|
function renderOTPLoginSettingsContent(plugin, settings) {
|
|
20007
20269
|
const siteName = settings.siteName || "SonicJS";
|
|
@@ -20724,6 +20986,62 @@ defineUserProfile({
|
|
|
20724
20986
|
</div>
|
|
20725
20987
|
`;
|
|
20726
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
|
+
}
|
|
20727
21045
|
|
|
20728
21046
|
// src/plugins/core-plugins/menu-plugin/services/menu-reconcile.ts
|
|
20729
21047
|
async function upsertPluginRow(db, slug, entry, visible) {
|
|
@@ -27084,6 +27402,6 @@ var ROUTES_INFO = {
|
|
|
27084
27402
|
reference: "https://github.com/sonicjs/sonicjs"
|
|
27085
27403
|
};
|
|
27086
27404
|
|
|
27087
|
-
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, getConfirmationDialogScript2 as getConfirmationDialogScript, getCustomData, getEmailService, getUserProfileConfig, getVariablesCached, hasEmailService, invalidateVariablesCache, isValidMemberRole, lexicalEditorPlugin, reconcileMenuFromPlugins, renderConfirmationDialog2 as renderConfirmationDialog, renderTable2 as renderTable, resolveDocScope, resolveVariablesInObject, router, router2, setEmailService, setVariablesCache, test_cleanup_default, userProfilesPlugin, userRoutes };
|
|
27088
|
-
//# sourceMappingURL=chunk-
|
|
27089
|
-
//# sourceMappingURL=chunk-
|
|
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 };
|
|
27406
|
+
//# sourceMappingURL=chunk-7P5OCUPS.js.map
|
|
27407
|
+
//# sourceMappingURL=chunk-7P5OCUPS.js.map
|