@sonicjs-cms/core 3.0.0-beta.24 → 3.0.0-beta.26
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/{app-01M0dIwt.d.ts → app-BP5IlK94.d.ts} +7 -7
- package/dist/{app-aj39TGHB.d.cts → app-fdq4K3vw.d.cts} +7 -7
- package/dist/catalog-56DR6OXI.cjs +41 -0
- package/dist/catalog-56DR6OXI.cjs.map +1 -0
- package/dist/catalog-6MB37KY7.js +4 -0
- package/dist/catalog-6MB37KY7.js.map +1 -0
- package/dist/{chunk-QCJHCSOK.cjs → chunk-4DHPR7KR.cjs} +77 -17
- package/dist/chunk-4DHPR7KR.cjs.map +1 -0
- package/dist/{chunk-UIHIXTIS.js → chunk-5YSFQO7X.js} +40 -13
- package/dist/chunk-5YSFQO7X.js.map +1 -0
- package/dist/{chunk-GMMCCIQO.cjs → chunk-6UAI4SGN.cjs} +3 -3
- package/dist/{chunk-GMMCCIQO.cjs.map → chunk-6UAI4SGN.cjs.map} +1 -1
- package/dist/{chunk-2XAQTPAE.js → chunk-6X22VFEV.js} +8 -2
- package/dist/chunk-6X22VFEV.js.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-QJUJNTIU.cjs → chunk-A22RBHO7.cjs} +43 -12
- package/dist/chunk-A22RBHO7.cjs.map +1 -0
- package/dist/{chunk-QZGABF2M.js → chunk-AMP4BSJE.js} +18 -7
- package/dist/chunk-AMP4BSJE.js.map +1 -0
- package/dist/{chunk-37V3RET3.cjs → chunk-BRKXMRMB.cjs} +99 -98
- package/dist/chunk-BRKXMRMB.cjs.map +1 -0
- package/dist/{chunk-36Z3AKX3.cjs → chunk-C5LDQ3SH.cjs} +8 -2
- package/dist/chunk-C5LDQ3SH.cjs.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-BHND5PEV.js → chunk-E3TQG7AA.js} +3 -3
- package/dist/{chunk-BHND5PEV.js.map → chunk-E3TQG7AA.js.map} +1 -1
- package/dist/{chunk-FHPEM77Y.js → chunk-EEA476CJ.js} +88 -83
- package/dist/chunk-EEA476CJ.js.map +1 -0
- package/dist/chunk-F6BTBE4S.js +119 -0
- package/dist/chunk-F6BTBE4S.js.map +1 -0
- package/dist/chunk-FZGIU5AZ.cjs +128 -0
- package/dist/chunk-FZGIU5AZ.cjs.map +1 -0
- package/dist/{chunk-BJROXF4N.js → chunk-HSDJDWLV.js} +19 -4
- package/dist/chunk-HSDJDWLV.js.map +1 -0
- package/dist/{chunk-VGEZNFWH.js → chunk-JD7BZU3A.js} +5 -4
- package/dist/chunk-JD7BZU3A.js.map +1 -0
- package/dist/{chunk-SOZUG52O.js → chunk-JG5J434C.js} +3 -3
- package/dist/{chunk-SOZUG52O.js.map → chunk-JG5J434C.js.map} +1 -1
- package/dist/{chunk-BP52BFIV.cjs → chunk-KWQNB6UM.cjs} +6 -5
- package/dist/chunk-KWQNB6UM.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-VC5XVPLM.js → chunk-OJFIQGZB.js} +512 -148
- package/dist/chunk-OJFIQGZB.js.map +1 -0
- package/dist/{chunk-LTZ5WYOK.cjs → chunk-OLEOKHDX.cjs} +25 -2
- package/dist/chunk-OLEOKHDX.cjs.map +1 -0
- package/dist/{chunk-E66UVONV.cjs → chunk-OUBUS74I.cjs} +24 -9
- package/dist/chunk-OUBUS74I.cjs.map +1 -0
- package/dist/{chunk-A2SN3RVL.cjs → chunk-PAS7GZBS.cjs} +40 -13
- package/dist/chunk-PAS7GZBS.cjs.map +1 -0
- package/dist/{chunk-RJ2O5IIE.js → chunk-RVOX2WH2.js} +74 -18
- package/dist/chunk-RVOX2WH2.js.map +1 -0
- package/dist/{chunk-3TMTYJAJ.js → chunk-RXLI3ADU.js} +25 -2
- package/dist/chunk-RXLI3ADU.js.map +1 -0
- package/dist/{chunk-I767LVN7.cjs → chunk-S2QN4LFD.cjs} +881 -515
- package/dist/chunk-S2QN4LFD.cjs.map +1 -0
- package/dist/{chunk-IXUHXTHW.cjs → chunk-SFGYZUWU.cjs} +18 -7
- package/dist/chunk-SFGYZUWU.cjs.map +1 -0
- 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-LNI3FYSI.js → chunk-UB5QSRO7.js} +9 -14
- package/dist/chunk-UB5QSRO7.js.map +1 -0
- package/dist/{chunk-VYQVDL2M.js → chunk-UKIDWRA3.js} +36 -5
- package/dist/chunk-UKIDWRA3.js.map +1 -0
- package/dist/{chunk-KKNEYIZJ.cjs → chunk-XZUEGFXE.cjs} +9 -14
- package/dist/chunk-XZUEGFXE.cjs.map +1 -0
- package/dist/{collection-config-BTzAvKt2.d.cts → collection-config-U2dydD_O.d.cts} +14 -0
- package/dist/{collection-config-BTzAvKt2.d.ts → collection-config-U2dydD_O.d.ts} +14 -0
- package/dist/config-DOAWXZBG.js +6 -0
- package/dist/{config-6FB2BXMP.js.map → config-DOAWXZBG.js.map} +1 -1
- package/dist/{config-OCH7S4WK.cjs → config-VLU73ZW7.cjs} +5 -5
- package/dist/{config-OCH7S4WK.cjs.map → config-VLU73ZW7.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 +698 -392
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -12
- package/dist/index.d.ts +27 -12
- package/dist/index.js +340 -35
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +47 -47
- package/dist/middleware.d.cts +10 -10
- package/dist/middleware.d.ts +10 -10
- package/dist/middleware.js +6 -6
- package/dist/migrations-6M6772UC.cjs +13 -0
- package/dist/{migrations-FGXZPKDC.cjs.map → migrations-6M6772UC.cjs.map} +1 -1
- package/dist/migrations-GZ2HIB4I.js +4 -0
- package/dist/{migrations-Z6D27NAW.js.map → migrations-GZ2HIB4I.js.map} +1 -1
- package/dist/{plugin-bootstrap-BHWQ2df8.d.cts → plugin-bootstrap-CfVILQeP.d.ts} +1 -1
- package/dist/{plugin-bootstrap-Bj4fxX1q.d.ts → plugin-bootstrap-RY5pkoOT.d.cts} +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-TFUOYIVN.cjs +14 -0
- package/dist/{rbac-7QZ4JZ2E.cjs.map → rbac-TFUOYIVN.cjs.map} +1 -1
- package/dist/rbac-XSNP76CV.js +5 -0
- package/dist/{rbac-Y43IEQOP.js.map → rbac-XSNP76CV.js.map} +1 -1
- package/dist/routes.cjs +33 -32
- package/dist/routes.d.cts +3 -5
- package/dist/routes.d.ts +3 -5
- package/dist/routes.js +13 -12
- package/dist/services.cjs +30 -30
- package/dist/services.d.cts +10 -3
- package/dist/services.d.ts +10 -3
- package/dist/services.js +3 -3
- 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 +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/utils.cjs +12 -12
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -2
- package/package.json +7 -12
- 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-2XAQTPAE.js.map +0 -1
- package/dist/chunk-36Z3AKX3.cjs.map +0 -1
- package/dist/chunk-37V3RET3.cjs.map +0 -1
- package/dist/chunk-3TMTYJAJ.js.map +0 -1
- package/dist/chunk-A2SN3RVL.cjs.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-QCJHCSOK.cjs.map +0 -1
- package/dist/chunk-QJUJNTIU.cjs.map +0 -1
- package/dist/chunk-QZGABF2M.js.map +0 -1
- package/dist/chunk-RJ2O5IIE.js.map +0 -1
- package/dist/chunk-THFZSDIU.js.map +0 -1
- package/dist/chunk-UIHIXTIS.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,19 @@
|
|
|
1
|
+
import { renderDocumentFormPage, renderAlert } from './chunk-E3TQG7AA.js';
|
|
2
|
+
import { recordCatalogRequest, scheduleKvWrite } from './chunk-F6BTBE4S.js';
|
|
1
3
|
import { isFirstUserRegistration, isRegistrationEnabled, authValidationService } from './chunk-VVYVFCML.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { DocumentsService, documentSecondsToMs, RbacService } from './chunk-3TMTYJAJ.js';
|
|
4
|
+
import { getCacheService, CACHE_CONFIGS, SettingsService, getLogger, getAppInstance, buildRouteList, CATEGORY_INFO } from './chunk-6X22VFEV.js';
|
|
5
|
+
import { DocumentTypeRegistry, requireAuth, requireRole, optionalAuth, rateLimit, AuthManager, getJwtExpirySecondsFromDb, getJwtRefreshGraceSecondsFromDb, logActivity, generateCsrfToken } from './chunk-EEA476CJ.js';
|
|
6
|
+
import { DocumentsService, documentSecondsToMs, RbacService } from './chunk-RXLI3ADU.js';
|
|
6
7
|
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-
|
|
8
|
+
import { getCollectionRegistry, isPluginActive, collectionRecordToRow, loadCollectionConfigs, getVisibleCollections, PluginService, PLUGIN_REGISTRY, findPluginByCodeName, isCodeCollectionInternal, isDbDocTypeInternal, invalidateTenantCache } from './chunk-RVOX2WH2.js';
|
|
9
|
+
import { MigrationService } from './chunk-5YSFQO7X.js';
|
|
10
|
+
import { renderCheckboxPage, renderAlert as renderAlert$1, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-HSDJDWLV.js';
|
|
11
|
+
import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-SXC4HPK6.js';
|
|
12
|
+
import { createServiceSingleton, definePlugin, getPluginDefinition, applySchemaDefaults, renderSchemaFields, parseFormDataToSettings, getPluginMenu } from './chunk-JD7BZU3A.js';
|
|
12
13
|
import { hasHookSystem, getTypedHooks } from './chunk-RNZFGN4R.js';
|
|
13
|
-
import { QueryFilterBuilder, getBlocksFieldConfig, parseBlocksValue } from './chunk-
|
|
14
|
+
import { QueryFilterBuilder, getBlocksFieldConfig, parseBlocksValue } from './chunk-AMP4BSJE.js';
|
|
14
15
|
import { metricsTracker } from './chunk-FICTAGD4.js';
|
|
15
|
-
import { getCoreVersion } from './chunk-
|
|
16
|
+
import { getCoreVersion } from './chunk-UB5QSRO7.js';
|
|
16
17
|
import { escapeHtml, sanitizeRichText, sanitizeInput } from './chunk-TQABQWOP.js';
|
|
17
18
|
import { Hono } from 'hono';
|
|
18
19
|
import { z } from 'zod';
|
|
@@ -45,6 +46,7 @@ var DocumentPermissionsService = class {
|
|
|
45
46
|
const publicGrants = baseGrants["public"] ?? [];
|
|
46
47
|
if (publicGrants.includes(permission)) return true;
|
|
47
48
|
} else if (principal.type === "role") {
|
|
49
|
+
if (principal.id === "public") continue;
|
|
48
50
|
const roleGrants = baseGrants[principal.id] ?? [];
|
|
49
51
|
if (roleGrants.includes(permission)) return true;
|
|
50
52
|
}
|
|
@@ -842,6 +844,34 @@ async function dispatchHookEvent(c, event, payload, mode = "fire-and-forget") {
|
|
|
842
844
|
return hooks.dispatch(event, payload);
|
|
843
845
|
}
|
|
844
846
|
|
|
847
|
+
// src/plugins/cache/services/swr.ts
|
|
848
|
+
var STALE_TTL_MS = 3e4;
|
|
849
|
+
var staleMap = /* @__PURE__ */ new Map();
|
|
850
|
+
function markStale(cacheKey, data, collection) {
|
|
851
|
+
staleMap.set(cacheKey, {
|
|
852
|
+
data,
|
|
853
|
+
collection,
|
|
854
|
+
expiresAt: Date.now() + STALE_TTL_MS
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
function getAndConsumeStale(cacheKey) {
|
|
858
|
+
const entry = staleMap.get(cacheKey);
|
|
859
|
+
if (!entry) return null;
|
|
860
|
+
if (Date.now() > entry.expiresAt) {
|
|
861
|
+
staleMap.delete(cacheKey);
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
staleMap.delete(cacheKey);
|
|
865
|
+
return entry.data;
|
|
866
|
+
}
|
|
867
|
+
function swrStats() {
|
|
868
|
+
const now = Date.now();
|
|
869
|
+
for (const [key, entry] of staleMap.entries()) {
|
|
870
|
+
if (now > entry.expiresAt) staleMap.delete(key);
|
|
871
|
+
}
|
|
872
|
+
return { count: staleMap.size };
|
|
873
|
+
}
|
|
874
|
+
|
|
845
875
|
// src/plugins/core-plugins/global-variables-plugin/variable-resolver.ts
|
|
846
876
|
var TOKEN_PATTERN = /\{([a-z0-9_]+)\}/g;
|
|
847
877
|
function resolveVariables(text, variables) {
|
|
@@ -994,6 +1024,32 @@ apiContentCrudRoutes.get("/:id", optionalAuth(), async (c) => {
|
|
|
994
1024
|
{ collection: docRow.type_id, id: docRow.root_id, data: transformedContent.data },
|
|
995
1025
|
"fire-and-forget"
|
|
996
1026
|
);
|
|
1027
|
+
const fieldsParam = c.req.query("fields");
|
|
1028
|
+
if (fieldsParam) {
|
|
1029
|
+
const projFields = fieldsParam.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1030
|
+
if (projFields.length > 0) {
|
|
1031
|
+
const result = {};
|
|
1032
|
+
const dataSubFields = [];
|
|
1033
|
+
for (const f of projFields) {
|
|
1034
|
+
if (f === "data") {
|
|
1035
|
+
result.data = transformedContent.data;
|
|
1036
|
+
} else if (f.startsWith("data.")) {
|
|
1037
|
+
dataSubFields.push(f.slice(5));
|
|
1038
|
+
} else if (Object.prototype.hasOwnProperty.call(transformedContent, f)) {
|
|
1039
|
+
result[f] = transformedContent[f];
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
if (dataSubFields.length > 0 && !result.data) {
|
|
1043
|
+
result.data = {};
|
|
1044
|
+
for (const sub of dataSubFields) {
|
|
1045
|
+
if (transformedContent.data && Object.prototype.hasOwnProperty.call(transformedContent.data, sub)) {
|
|
1046
|
+
result.data[sub] = transformedContent.data[sub];
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return c.json({ data: result });
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
997
1053
|
return c.json({ data: transformedContent });
|
|
998
1054
|
} catch (error) {
|
|
999
1055
|
console.error("Error fetching content:", error);
|
|
@@ -1057,8 +1113,8 @@ apiContentCrudRoutes.post("/", requireAuth(), requireRole(["admin", "editor", "a
|
|
|
1057
1113
|
user?.userId
|
|
1058
1114
|
);
|
|
1059
1115
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1060
|
-
await cache.invalidate("content-filtered:*");
|
|
1061
|
-
await cache.invalidate(
|
|
1116
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1117
|
+
await cache.invalidate(`api:collection-content-filtered:${backing.coll.name}:*`);
|
|
1062
1118
|
dispatchHookEvent(
|
|
1063
1119
|
c,
|
|
1064
1120
|
"content:after:create",
|
|
@@ -1121,8 +1177,8 @@ apiContentCrudRoutes.put("/:id", requireAuth(), requireRole(["admin", "editor",
|
|
|
1121
1177
|
await svc.unpublish(pub.id);
|
|
1122
1178
|
}
|
|
1123
1179
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1124
|
-
await cache.invalidate("content-filtered:*");
|
|
1125
|
-
await cache.invalidate(
|
|
1180
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1181
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
1126
1182
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
1127
1183
|
const saved = await db.prepare("SELECT * FROM documents WHERE id = ?").bind(newDraft.id).first();
|
|
1128
1184
|
const savedData = saved?.data ? JSON.parse(saved.data) : {};
|
|
@@ -1161,8 +1217,8 @@ apiContentCrudRoutes.delete("/:id", requireAuth(), requireRole(["admin", "editor
|
|
|
1161
1217
|
const now = Math.floor(Date.now() / 1e3);
|
|
1162
1218
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = ?").bind(now, now, id, tenantId).run();
|
|
1163
1219
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1164
|
-
await cache.invalidate("content-filtered:*");
|
|
1165
|
-
await cache.invalidate(
|
|
1220
|
+
await cache.invalidate("api:content-filtered:*");
|
|
1221
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
1166
1222
|
dispatchHookEvent(c, "content:after:delete", { collection: docRow.type_id, id, data: {}, user: actor }, "fire-and-forget");
|
|
1167
1223
|
return c.json({ success: true });
|
|
1168
1224
|
}
|
|
@@ -1178,6 +1234,22 @@ apiContentCrudRoutes.delete("/:id", requireAuth(), requireRole(["admin", "editor
|
|
|
1178
1234
|
var api_content_crud_default = apiContentCrudRoutes;
|
|
1179
1235
|
|
|
1180
1236
|
// src/routes/api.ts
|
|
1237
|
+
function isAnonPrincipal(principalSet) {
|
|
1238
|
+
return principalSet.length === 1 && principalSet[0]?.type === "public";
|
|
1239
|
+
}
|
|
1240
|
+
async function typeReadAllowed(db, principalSet, docType, typeName) {
|
|
1241
|
+
const perms = new DocumentPermissionsService(db);
|
|
1242
|
+
if (perms.isAllowedSync(principalSet, [], "read", docType?.settings ?? {})) return true;
|
|
1243
|
+
const rbac = new RbacService(db);
|
|
1244
|
+
let anyRbacRoleFound = false;
|
|
1245
|
+
for (const p of principalSet) {
|
|
1246
|
+
if (p.type !== "role") continue;
|
|
1247
|
+
if (!await rbac.hasRbacRole(p.id)) continue;
|
|
1248
|
+
anyRbacRoleFound = true;
|
|
1249
|
+
if (await rbac.isGrantedForRole(p.id, `document_type:${typeName}`, "read")) return true;
|
|
1250
|
+
}
|
|
1251
|
+
return !anyRbacRoleFound;
|
|
1252
|
+
}
|
|
1181
1253
|
var DOC_SORTABLE_COLUMNS = /* @__PURE__ */ new Set([
|
|
1182
1254
|
"created_at",
|
|
1183
1255
|
"updated_at",
|
|
@@ -1261,6 +1333,33 @@ function mapDocRowToContent(row, collectionId) {
|
|
|
1261
1333
|
updated_at: documentSecondsToMs(row.updated_at)
|
|
1262
1334
|
};
|
|
1263
1335
|
}
|
|
1336
|
+
function projectFields(item, fields) {
|
|
1337
|
+
if (fields.length === 0) return item;
|
|
1338
|
+
const result = {};
|
|
1339
|
+
const dataSubFields = [];
|
|
1340
|
+
for (const f of fields) {
|
|
1341
|
+
if (f === "data") {
|
|
1342
|
+
result.data = item.data;
|
|
1343
|
+
} else if (f.startsWith("data.")) {
|
|
1344
|
+
dataSubFields.push(f.slice(5));
|
|
1345
|
+
} else if (Object.prototype.hasOwnProperty.call(item, f)) {
|
|
1346
|
+
result[f] = item[f];
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (dataSubFields.length > 0 && !result.data) {
|
|
1350
|
+
result.data = {};
|
|
1351
|
+
for (const sub of dataSubFields) {
|
|
1352
|
+
if (item.data && Object.prototype.hasOwnProperty.call(item.data, sub)) {
|
|
1353
|
+
result.data[sub] = item.data[sub];
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return result;
|
|
1358
|
+
}
|
|
1359
|
+
function parseFieldsParam(raw2) {
|
|
1360
|
+
if (!raw2) return [];
|
|
1361
|
+
return raw2.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1362
|
+
}
|
|
1264
1363
|
var apiRoutes = new Hono();
|
|
1265
1364
|
apiRoutes.use("*", async (c, next) => {
|
|
1266
1365
|
const startTime = Date.now();
|
|
@@ -1270,7 +1369,8 @@ apiRoutes.use("*", async (c, next) => {
|
|
|
1270
1369
|
c.header("X-Response-Time", `${totalTime}ms`);
|
|
1271
1370
|
});
|
|
1272
1371
|
apiRoutes.use("*", async (c, next) => {
|
|
1273
|
-
const
|
|
1372
|
+
const bypass = c.req.header("Cache-Control") === "no-cache";
|
|
1373
|
+
const cacheEnabled = !bypass && await isPluginActive(c.env.DB, "core-cache");
|
|
1274
1374
|
c.set("cacheEnabled", cacheEnabled);
|
|
1275
1375
|
await next();
|
|
1276
1376
|
});
|
|
@@ -1750,7 +1850,12 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1750
1850
|
try {
|
|
1751
1851
|
const db = c.env.DB;
|
|
1752
1852
|
const queryParams = c.req.query();
|
|
1853
|
+
const projFields = parseFieldsParam(queryParams.fields);
|
|
1854
|
+
if (queryParams.fields) delete queryParams.fields;
|
|
1753
1855
|
const role = c.get("user")?.role;
|
|
1856
|
+
const { tenantId: cTenantId, principalSet: cPrincipalSet } = getDocumentRequestContext(c);
|
|
1857
|
+
const cAnon = isAnonPrincipal(cPrincipalSet);
|
|
1858
|
+
const cNeedsAcl = !cAnon && !canReadNonPublicContent(role);
|
|
1754
1859
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1755
1860
|
let typeId;
|
|
1756
1861
|
let typeIds;
|
|
@@ -1796,7 +1901,7 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1796
1901
|
details: queryResult.errors
|
|
1797
1902
|
}, 400);
|
|
1798
1903
|
}
|
|
1799
|
-
const cacheEnabled = c.get("cacheEnabled");
|
|
1904
|
+
const cacheEnabled = c.get("cacheEnabled") && !cNeedsAcl;
|
|
1800
1905
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1801
1906
|
const cacheKey = cache.generateKey("content-filtered", JSON.stringify({ filter: normalizedFilter, query: queryResult.sql }));
|
|
1802
1907
|
if (cacheEnabled) {
|
|
@@ -1818,19 +1923,61 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1818
1923
|
}
|
|
1819
1924
|
}, executionStart)
|
|
1820
1925
|
};
|
|
1926
|
+
recordCatalogRequest({ cacheKey, collection: typeId ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: cacheResult.source });
|
|
1927
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
1821
1928
|
return c.json(dataWithMeta);
|
|
1822
1929
|
}
|
|
1930
|
+
const swrData = getAndConsumeStale(cacheKey);
|
|
1931
|
+
if (swrData) {
|
|
1932
|
+
c.header("X-Cache-Status", "STALE");
|
|
1933
|
+
c.header("X-Cache-Source", "swr");
|
|
1934
|
+
recordCatalogRequest({ cacheKey, collection: typeId ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: "swr" });
|
|
1935
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
1936
|
+
return c.json({ ...swrData, meta: addTimingMeta(c, { ...swrData.meta, cache: { hit: true, source: "swr", stale: true } }, executionStart) });
|
|
1937
|
+
}
|
|
1938
|
+
recordCatalogRequest({ cacheKey, collection: typeId ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: "miss" });
|
|
1939
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
1823
1940
|
}
|
|
1824
1941
|
c.header("X-Cache-Status", "MISS");
|
|
1825
1942
|
c.header("X-Cache-Source", "database");
|
|
1826
1943
|
const stmt = db.prepare(queryResult.sql);
|
|
1827
1944
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1828
1945
|
const { results } = await boundStmt.all();
|
|
1829
|
-
|
|
1946
|
+
let aclResults = results;
|
|
1947
|
+
if (cNeedsAcl) {
|
|
1948
|
+
const typeReg = new DocumentTypeRegistry(db);
|
|
1949
|
+
const repo = new DocumentRepository(db, cTenantId);
|
|
1950
|
+
const uniqueTypeIds = [...new Set(results.map((r) => r.type_id))];
|
|
1951
|
+
const typeSettingsMap = /* @__PURE__ */ new Map();
|
|
1952
|
+
const typeRbacOnlyMap = /* @__PURE__ */ new Map();
|
|
1953
|
+
const typeDeniedMap = /* @__PURE__ */ new Map();
|
|
1954
|
+
await Promise.all(uniqueTypeIds.map(async (tid) => {
|
|
1955
|
+
const dt = await typeReg.findById(tid);
|
|
1956
|
+
const settings = dt?.settings ?? {};
|
|
1957
|
+
typeSettingsMap.set(tid, settings);
|
|
1958
|
+
const allowed2 = await typeReadAllowed(db, cPrincipalSet, dt, tid);
|
|
1959
|
+
if (!allowed2) {
|
|
1960
|
+
typeDeniedMap.set(tid, true);
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
const perms = new DocumentPermissionsService(db);
|
|
1964
|
+
typeRbacOnlyMap.set(tid, !perms.isAllowedSync(cPrincipalSet, [], "read", settings));
|
|
1965
|
+
}));
|
|
1966
|
+
const allowed = await Promise.all(
|
|
1967
|
+
results.map((row) => {
|
|
1968
|
+
const tid = row.type_id;
|
|
1969
|
+
if (typeDeniedMap.get(tid)) return Promise.resolve(false);
|
|
1970
|
+
if (typeRbacOnlyMap.get(tid)) return Promise.resolve(true);
|
|
1971
|
+
return repo.isAllowed(cPrincipalSet, row.root_id, "read", typeSettingsMap.get(tid) ?? {});
|
|
1972
|
+
})
|
|
1973
|
+
);
|
|
1974
|
+
aclResults = results.filter((_, i) => allowed[i]);
|
|
1975
|
+
}
|
|
1976
|
+
const transformedResults = aclResults.map((row) => mapDocRowToContent(row, collIdByName.get(row.type_id) ?? null));
|
|
1830
1977
|
const responseData = {
|
|
1831
|
-
data: transformedResults,
|
|
1978
|
+
data: projFields.length ? transformedResults.map((item) => projectFields(item, projFields)) : transformedResults,
|
|
1832
1979
|
meta: addTimingMeta(c, {
|
|
1833
|
-
count:
|
|
1980
|
+
count: aclResults.length,
|
|
1834
1981
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1835
1982
|
// D44: echo the caller's filter with the access policy applied (status=published forced for
|
|
1836
1983
|
// anonymous callers — the visible enforcement proof), NOT the internal doc-augmented where-tree.
|
|
@@ -1841,7 +1988,7 @@ apiRoutes.get("/content", optionalAuth(), async (c) => {
|
|
|
1841
1988
|
}
|
|
1842
1989
|
}, executionStart)
|
|
1843
1990
|
};
|
|
1844
|
-
if (cacheEnabled) {
|
|
1991
|
+
if (cacheEnabled && projFields.length === 0) {
|
|
1845
1992
|
await cache.set(cacheKey, responseData);
|
|
1846
1993
|
}
|
|
1847
1994
|
return c.json(responseData);
|
|
@@ -1866,6 +2013,16 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1866
2013
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1867
2014
|
collIdByName.set(collection, record.id);
|
|
1868
2015
|
const role = c.get("user")?.role;
|
|
2016
|
+
const { tenantId: ccTenantId, principalSet: ccPrincipalSet } = getDocumentRequestContext(c);
|
|
2017
|
+
const ccAnon = isAnonPrincipal(ccPrincipalSet);
|
|
2018
|
+
const ccNeedsAcl = !ccAnon && !canReadNonPublicContent(role);
|
|
2019
|
+
let ccDocType;
|
|
2020
|
+
if (ccNeedsAcl) {
|
|
2021
|
+
ccDocType = await new DocumentTypeRegistry(db).findById(record.name);
|
|
2022
|
+
if (!await typeReadAllowed(db, ccPrincipalSet, ccDocType, record.name)) {
|
|
2023
|
+
return c.json({ error: "Forbidden" }, 403);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
1869
2026
|
const filter = QueryFilterBuilder.parseFromQuery(queryParams);
|
|
1870
2027
|
const normalizedFilter = augmentFilterForDocuments(filter, { typeId: collection, role });
|
|
1871
2028
|
if (!normalizedFilter.limit) {
|
|
@@ -1880,7 +2037,7 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1880
2037
|
details: queryResult.errors
|
|
1881
2038
|
}, 400);
|
|
1882
2039
|
}
|
|
1883
|
-
const cacheEnabled = c.get("cacheEnabled");
|
|
2040
|
+
const cacheEnabled = c.get("cacheEnabled") && !ccNeedsAcl;
|
|
1884
2041
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1885
2042
|
const includeCollection = queryParams.include?.split(",").map((s) => s.trim()).includes("collection");
|
|
1886
2043
|
const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql, includeCollection })}`);
|
|
@@ -1903,20 +2060,44 @@ apiRoutes.get("/collections/:collection/content", optionalAuth(), async (c) => {
|
|
|
1903
2060
|
}
|
|
1904
2061
|
}, executionStart)
|
|
1905
2062
|
};
|
|
2063
|
+
recordCatalogRequest({ cacheKey, collection: collection ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: cacheResult.source });
|
|
2064
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
1906
2065
|
return c.json(dataWithMeta);
|
|
1907
2066
|
}
|
|
2067
|
+
const swrData = getAndConsumeStale(cacheKey);
|
|
2068
|
+
if (swrData) {
|
|
2069
|
+
c.header("X-Cache-Status", "STALE");
|
|
2070
|
+
c.header("X-Cache-Source", "swr");
|
|
2071
|
+
recordCatalogRequest({ cacheKey, collection: collection ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: "swr" });
|
|
2072
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
2073
|
+
return c.json({ ...swrData, meta: addTimingMeta(c, { ...swrData.meta, cache: { hit: true, source: "swr", stale: true } }, executionStart) });
|
|
2074
|
+
}
|
|
2075
|
+
recordCatalogRequest({ cacheKey, collection: collection ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: "miss" });
|
|
2076
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
1908
2077
|
}
|
|
1909
2078
|
c.header("X-Cache-Status", "MISS");
|
|
1910
2079
|
c.header("X-Cache-Source", "database");
|
|
1911
2080
|
const stmt = db.prepare(queryResult.sql);
|
|
1912
2081
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1913
2082
|
const { results } = await boundStmt.all();
|
|
1914
|
-
|
|
2083
|
+
let ccAclResults = results;
|
|
2084
|
+
if (ccNeedsAcl) {
|
|
2085
|
+
const ccPerms = new DocumentPermissionsService(db);
|
|
2086
|
+
const ccBaseGrantsPass = ccPerms.isAllowedSync(ccPrincipalSet, [], "read", ccDocType?.settings ?? {});
|
|
2087
|
+
if (ccBaseGrantsPass) {
|
|
2088
|
+
const repo = new DocumentRepository(db, ccTenantId);
|
|
2089
|
+
const allowed = await Promise.all(
|
|
2090
|
+
results.map((row) => repo.isAllowed(ccPrincipalSet, row.root_id, "read", ccDocType?.settings ?? {}))
|
|
2091
|
+
);
|
|
2092
|
+
ccAclResults = results.filter((_, i) => allowed[i]);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
const transformedResults = ccAclResults.map((row) => mapDocRowToContent(row, collIdByName.get(row.type_id) ?? null));
|
|
1915
2096
|
const responseData = {
|
|
1916
2097
|
data: transformedResults,
|
|
1917
2098
|
meta: addTimingMeta(c, {
|
|
1918
2099
|
...includeCollection ? { collection: collectionRecordToRow(record) } : {},
|
|
1919
|
-
count:
|
|
2100
|
+
count: ccAclResults.length,
|
|
1920
2101
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1921
2102
|
// D44: echo the caller's filter with the access policy applied (status=published forced for
|
|
1922
2103
|
// anonymous callers — the visible enforcement proof), NOT the internal doc-augmented where-tree.
|
|
@@ -1946,6 +2127,8 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1946
2127
|
const collection = c.req.param("collection");
|
|
1947
2128
|
const db = c.env.DB;
|
|
1948
2129
|
const queryParams = c.req.query();
|
|
2130
|
+
const projFields = parseFieldsParam(queryParams.fields);
|
|
2131
|
+
if (queryParams.fields) delete queryParams.fields;
|
|
1949
2132
|
const record = getCollectionRegistry().getBySlugOrName(collection);
|
|
1950
2133
|
if (!record || record.isActive === false) {
|
|
1951
2134
|
return c.json({ error: "Collection not found" }, 404);
|
|
@@ -1953,6 +2136,16 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1953
2136
|
const collIdByName = /* @__PURE__ */ new Map();
|
|
1954
2137
|
collIdByName.set(record.name, record.id);
|
|
1955
2138
|
const role = c.get("user")?.role;
|
|
2139
|
+
const { tenantId, principalSet } = getDocumentRequestContext(c);
|
|
2140
|
+
const anon = isAnonPrincipal(principalSet);
|
|
2141
|
+
const needsAcl = !anon && !canReadNonPublicContent(role);
|
|
2142
|
+
let docTypeForCheck;
|
|
2143
|
+
if (needsAcl) {
|
|
2144
|
+
docTypeForCheck = await new DocumentTypeRegistry(db).findById(record.name);
|
|
2145
|
+
if (!await typeReadAllowed(db, principalSet, docTypeForCheck, record.name)) {
|
|
2146
|
+
return c.json({ error: "Forbidden" }, 403);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
1956
2149
|
const filter = QueryFilterBuilder.parseFromQuery(queryParams);
|
|
1957
2150
|
const normalizedFilter = augmentFilterForDocuments(filter, { typeId: record.name, role });
|
|
1958
2151
|
if (!normalizedFilter.limit) normalizedFilter.limit = 50;
|
|
@@ -1964,7 +2157,7 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1964
2157
|
}
|
|
1965
2158
|
const collectionCache = record.cache;
|
|
1966
2159
|
const collectionCacheDisabled = collectionCache?.enabled === false;
|
|
1967
|
-
const cacheEnabled = c.get("cacheEnabled") && !collectionCacheDisabled;
|
|
2160
|
+
const cacheEnabled = c.get("cacheEnabled") && !collectionCacheDisabled && projFields.length === 0 && !needsAcl;
|
|
1968
2161
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
1969
2162
|
const includeCollection = queryParams.include?.split(",").map((s) => s.trim()).includes("collection");
|
|
1970
2163
|
const cacheKey = cache.generateKey("collection-content-filtered", `${collection}:${JSON.stringify({ filter: normalizedFilter, query: queryResult.sql, includeCollection })}`);
|
|
@@ -1974,20 +2167,45 @@ apiRoutes.get("/:collection", optionalAuth(), async (c) => {
|
|
|
1974
2167
|
c.header("X-Cache-Status", "HIT");
|
|
1975
2168
|
c.header("X-Cache-Source", cacheResult.source);
|
|
1976
2169
|
if (cacheResult.ttl) c.header("X-Cache-TTL", Math.floor(cacheResult.ttl).toString());
|
|
2170
|
+
recordCatalogRequest({ cacheKey, collection: collection ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: cacheResult.source });
|
|
2171
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
1977
2172
|
return c.json({ ...cacheResult.data, meta: addTimingMeta(c, { ...cacheResult.data.meta, cache: { hit: true, source: cacheResult.source, ttl: cacheResult.ttl ? Math.floor(cacheResult.ttl) : void 0 } }, executionStart) });
|
|
1978
2173
|
}
|
|
2174
|
+
const swrData = getAndConsumeStale(cacheKey);
|
|
2175
|
+
if (swrData) {
|
|
2176
|
+
c.header("X-Cache-Status", "STALE");
|
|
2177
|
+
c.header("X-Cache-Source", "swr");
|
|
2178
|
+
recordCatalogRequest({ cacheKey, collection: collection ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: "swr" });
|
|
2179
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
2180
|
+
return c.json({ ...swrData, meta: addTimingMeta(c, { ...swrData.meta, cache: { hit: true, source: "swr", stale: true } }, executionStart) });
|
|
2181
|
+
}
|
|
2182
|
+
recordCatalogRequest({ cacheKey, collection: collection ?? null, path: c.req.path, queryString: c.req.raw.url.split("?")[1] ?? "", source: "miss" });
|
|
2183
|
+
scheduleKvWrite(cacheKey, c.executionCtx);
|
|
1979
2184
|
}
|
|
1980
2185
|
c.header("X-Cache-Status", "MISS");
|
|
1981
2186
|
c.header("X-Cache-Source", "database");
|
|
1982
2187
|
const stmt = db.prepare(queryResult.sql);
|
|
1983
2188
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
1984
2189
|
const { results } = await boundStmt.all();
|
|
1985
|
-
|
|
2190
|
+
let aclResults = results;
|
|
2191
|
+
if (needsAcl) {
|
|
2192
|
+
const perms = new DocumentPermissionsService(db);
|
|
2193
|
+
const baseGrantsPass = perms.isAllowedSync(principalSet, [], "read", docTypeForCheck?.settings ?? {});
|
|
2194
|
+
if (!baseGrantsPass) {
|
|
2195
|
+
} else {
|
|
2196
|
+
const repo = new DocumentRepository(db, tenantId);
|
|
2197
|
+
const allowed = await Promise.all(
|
|
2198
|
+
results.map((row) => repo.isAllowed(principalSet, row.root_id, "read", docTypeForCheck?.settings ?? {}))
|
|
2199
|
+
);
|
|
2200
|
+
aclResults = results.filter((_, i) => allowed[i]);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
const transformedResults = aclResults.map((row) => mapDocRowToContent(row, collIdByName.get(row.type_id) ?? null));
|
|
1986
2204
|
const responseData = {
|
|
1987
|
-
data: transformedResults,
|
|
2205
|
+
data: projFields.length ? transformedResults.map((item) => projectFields(item, projFields)) : transformedResults,
|
|
1988
2206
|
meta: addTimingMeta(c, {
|
|
1989
2207
|
...includeCollection ? { collection: collectionRecordToRow(record) } : {},
|
|
1990
|
-
count:
|
|
2208
|
+
count: aclResults.length,
|
|
1991
2209
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1992
2210
|
filter: normalizePublicContentFilter(filter, role),
|
|
1993
2211
|
cache: { hit: false, source: "database" }
|
|
@@ -2018,6 +2236,23 @@ apiRoutes.get("/:collection/:id", optionalAuth(), async (c) => {
|
|
|
2018
2236
|
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
2237
|
).bind(id, record.name).first();
|
|
2020
2238
|
if (!docRow) return c.json({ error: "Content not found" }, 404);
|
|
2239
|
+
const colRole = c.get("user")?.role;
|
|
2240
|
+
const { tenantId: colTenantId, principalSet: colPrincipalSet } = getDocumentRequestContext(c);
|
|
2241
|
+
const colNeedsAcl = !isAnonPrincipal(colPrincipalSet) && !canReadNonPublicContent(colRole);
|
|
2242
|
+
if (colNeedsAcl) {
|
|
2243
|
+
const docType = await new DocumentTypeRegistry(db).findById(record.name);
|
|
2244
|
+
if (!await typeReadAllowed(db, colPrincipalSet, docType, record.name)) {
|
|
2245
|
+
return c.json({ error: "Content not found" }, 404);
|
|
2246
|
+
}
|
|
2247
|
+
const perms = new DocumentPermissionsService(db);
|
|
2248
|
+
if (perms.isAllowedSync(colPrincipalSet, [], "read", docType?.settings ?? {})) {
|
|
2249
|
+
const repo = new DocumentRepository(db, colTenantId);
|
|
2250
|
+
if (!await repo.isAllowed(colPrincipalSet, docRow.root_id, "read", docType?.settings ?? {})) {
|
|
2251
|
+
return c.json({ error: "Content not found" }, 404);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
const projFields = parseFieldsParam(c.req.query("fields"));
|
|
2021
2256
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
2022
2257
|
const transformedContent = {
|
|
2023
2258
|
id: docRow.root_id,
|
|
@@ -2030,7 +2265,7 @@ apiRoutes.get("/:collection/:id", optionalAuth(), async (c) => {
|
|
|
2030
2265
|
updated_at: documentSecondsToMs(docRow.updated_at)
|
|
2031
2266
|
};
|
|
2032
2267
|
dispatchHookEvent(c, "content:read", { collection: docRow.type_id, id: docRow.root_id, data: transformedContent.data }, "fire-and-forget");
|
|
2033
|
-
return c.json({ data: transformedContent });
|
|
2268
|
+
return c.json({ data: projFields.length ? projectFields(transformedContent, projFields) : transformedContent });
|
|
2034
2269
|
} catch (error) {
|
|
2035
2270
|
console.error("Error fetching content:", error);
|
|
2036
2271
|
return c.json({ error: "Failed to fetch content", details: error instanceof Error ? error.message : String(error) }, 500);
|
|
@@ -2060,8 +2295,11 @@ apiRoutes.post("/:collection", requireAuth(), requireRole(["admin", "editor", "a
|
|
|
2060
2295
|
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
2296
|
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
2297
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2063
|
-
await cache.
|
|
2064
|
-
await cache.
|
|
2298
|
+
const createAffectedKeys = (await cache.listKeys()).map((k) => k.key).filter((k) => k.startsWith("api:content-filtered:") || k.startsWith(`api:collection-content-filtered:${backing.coll.name}:`));
|
|
2299
|
+
const createStaleValues = await cache.getMany(createAffectedKeys);
|
|
2300
|
+
for (const [key, val] of createStaleValues) markStale(key, val, backing.coll.name);
|
|
2301
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2302
|
+
await cache.invalidate(`api:collection-content-filtered:${backing.coll.name}:*`);
|
|
2065
2303
|
dispatchHookEvent(c, "content:after:create", { collection: backing.coll.name, id: doc.rootId, data: doc.data ?? {}, user: actor }, "fire-and-forget");
|
|
2066
2304
|
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
2305
|
} catch (error) {
|
|
@@ -2103,8 +2341,11 @@ apiRoutes.put("/:collection/:id", requireAuth(), requireRole(["admin", "editor",
|
|
|
2103
2341
|
await svc.unpublish(pub.id);
|
|
2104
2342
|
}
|
|
2105
2343
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2106
|
-
await cache.
|
|
2107
|
-
await cache.
|
|
2344
|
+
const updateAffectedKeys = (await cache.listKeys()).map((k) => k.key).filter((k) => k.startsWith("api:content-filtered:") || k.startsWith(`api:collection-content-filtered:${docRow.type_id}:`));
|
|
2345
|
+
const updateStaleValues = await cache.getMany(updateAffectedKeys);
|
|
2346
|
+
for (const [key, val] of updateStaleValues) markStale(key, val, docRow.type_id);
|
|
2347
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2348
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
2108
2349
|
const coll = getCollectionRegistry().getByName(docRow.type_id);
|
|
2109
2350
|
const saved = await db.prepare("SELECT * FROM documents WHERE id = ?").bind(newDraft.id).first();
|
|
2110
2351
|
const savedData = saved?.data ? JSON.parse(saved.data) : {};
|
|
@@ -2136,8 +2377,11 @@ apiRoutes.delete("/:collection/:id", requireAuth(), requireRole(["admin", "edito
|
|
|
2136
2377
|
const now = Math.floor(Date.now() / 1e3);
|
|
2137
2378
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = 'default'").bind(now, now, id).run();
|
|
2138
2379
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
2139
|
-
await cache.
|
|
2140
|
-
await cache.
|
|
2380
|
+
const deleteAffectedKeys = (await cache.listKeys()).map((k) => k.key).filter((k) => k.startsWith("api:content-filtered:") || k.startsWith(`api:collection-content-filtered:${docRow.type_id}:`));
|
|
2381
|
+
const deleteStaleValues = await cache.getMany(deleteAffectedKeys);
|
|
2382
|
+
for (const [key, val] of deleteStaleValues) markStale(key, val, docRow.type_id);
|
|
2383
|
+
await cache.invalidate("api:content-filtered:*");
|
|
2384
|
+
await cache.invalidate(`api:collection-content-filtered:${docRow.type_id}:*`);
|
|
2141
2385
|
dispatchHookEvent(c, "content:after:delete", { collection: docRow.type_id, id, data: {}, user: actor }, "fire-and-forget");
|
|
2142
2386
|
return c.json({ success: true });
|
|
2143
2387
|
} catch (error) {
|
|
@@ -2699,9 +2943,13 @@ apiMediaRoutes.post("/create-folder", async (c) => {
|
|
|
2699
2943
|
error: "Folder name can only contain lowercase letters, numbers, hyphens, and underscores"
|
|
2700
2944
|
}, 400);
|
|
2701
2945
|
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2946
|
+
let folderExists = false;
|
|
2947
|
+
try {
|
|
2948
|
+
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();
|
|
2949
|
+
folderExists = (existing?.count ?? 0) > 0;
|
|
2950
|
+
} catch {
|
|
2951
|
+
}
|
|
2952
|
+
if (folderExists) {
|
|
2705
2953
|
return c.json({
|
|
2706
2954
|
success: false,
|
|
2707
2955
|
error: `Folder "${folderName}" already exists`
|
|
@@ -3411,7 +3659,7 @@ adminApiRoutes.patch("/collections/:id", collectionsReadOnly);
|
|
|
3411
3659
|
adminApiRoutes.delete("/collections/:id", collectionsReadOnly);
|
|
3412
3660
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
3413
3661
|
try {
|
|
3414
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
3662
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-GZ2HIB4I.js');
|
|
3415
3663
|
const db = c.env.DB;
|
|
3416
3664
|
const migrationService = new MigrationService2(db);
|
|
3417
3665
|
const status = await migrationService.getMigrationStatus();
|
|
@@ -3454,7 +3702,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
3454
3702
|
});
|
|
3455
3703
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
3456
3704
|
try {
|
|
3457
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
3705
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-GZ2HIB4I.js');
|
|
3458
3706
|
const db = c.env.DB;
|
|
3459
3707
|
const migrationService = new MigrationService2(db);
|
|
3460
3708
|
const validation = await migrationService.validateSchema();
|
|
@@ -3613,6 +3861,8 @@ function renderLoginPage(data, demoLoginActive = false) {
|
|
|
3613
3861
|
<!-- Form -->
|
|
3614
3862
|
<form
|
|
3615
3863
|
id="login-form"
|
|
3864
|
+
action="/auth/login/form${data.redirect ? `?redirect=${encodeURIComponent(data.redirect)}` : ""}"
|
|
3865
|
+
method="post"
|
|
3616
3866
|
hx-post="/auth/login/form${data.redirect ? `?redirect=${encodeURIComponent(data.redirect)}` : ""}"
|
|
3617
3867
|
hx-target="#form-response"
|
|
3618
3868
|
hx-swap="innerHTML"
|
|
@@ -5128,9 +5378,11 @@ function renderDynamicField(field, options = {}) {
|
|
|
5128
5378
|
min="${opts.min || ""}"
|
|
5129
5379
|
max="${opts.max || ""}"
|
|
5130
5380
|
class="${baseClasses} ${errorClasses} !w-auto dark:[color-scheme:dark]"
|
|
5381
|
+
placeholder="YYYY-MM-DD"
|
|
5131
5382
|
${required}
|
|
5132
5383
|
${disabled ? "disabled" : ""}
|
|
5133
5384
|
>
|
|
5385
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Format: YYYY-MM-DD</p>
|
|
5134
5386
|
`;
|
|
5135
5387
|
break;
|
|
5136
5388
|
case "datetime":
|
|
@@ -5146,6 +5398,7 @@ function renderDynamicField(field, options = {}) {
|
|
|
5146
5398
|
${required}
|
|
5147
5399
|
${disabled ? "disabled" : ""}
|
|
5148
5400
|
>
|
|
5401
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Format: YYYY-MM-DD HH:MM</p>
|
|
5149
5402
|
`;
|
|
5150
5403
|
break;
|
|
5151
5404
|
case "slug":
|
|
@@ -7100,8 +7353,32 @@ function createUserProfilesPlugin() {
|
|
|
7100
7353
|
return userProfilesPlugin;
|
|
7101
7354
|
}
|
|
7102
7355
|
|
|
7356
|
+
// src/services/demo-mode.ts
|
|
7357
|
+
var _demoModeActive = false;
|
|
7358
|
+
function setDemoModeActive() {
|
|
7359
|
+
_demoModeActive = true;
|
|
7360
|
+
}
|
|
7361
|
+
function isDemoModeActive() {
|
|
7362
|
+
return _demoModeActive;
|
|
7363
|
+
}
|
|
7364
|
+
|
|
7103
7365
|
// src/routes/auth.ts
|
|
7104
7366
|
var JWT_SECRET_FALLBACK = "your-super-secret-jwt-key-change-in-production";
|
|
7367
|
+
var DEMO_EMAIL = "admin@sonicjs.com";
|
|
7368
|
+
var STATS_EVENTS_ENDPOINT = "https://stats.sonicjs.com/v1/events";
|
|
7369
|
+
async function trackDemoLogin(kv) {
|
|
7370
|
+
try {
|
|
7371
|
+
const installationId = await kv?.get("_sonicjs_installation_id") ?? "demo-unknown";
|
|
7372
|
+
await fetch(STATS_EVENTS_ENDPOINT, {
|
|
7373
|
+
method: "POST",
|
|
7374
|
+
headers: { "Content-Type": "application/json" },
|
|
7375
|
+
body: JSON.stringify({
|
|
7376
|
+
data: { installation_id: installationId, event_type: "demo_login", properties: {}, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
7377
|
+
})
|
|
7378
|
+
});
|
|
7379
|
+
} catch {
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7105
7382
|
function renderPasswordResetEmail(resetLink, firstName) {
|
|
7106
7383
|
const greeting = firstName ? `Hi ${firstName},` : "Hello,";
|
|
7107
7384
|
return `<!DOCTYPE html>
|
|
@@ -7151,13 +7428,7 @@ authRoutes.get("/login", async (c) => {
|
|
|
7151
7428
|
version: c.get("appVersion"),
|
|
7152
7429
|
redirect: redirect && redirect.startsWith("/") ? redirect : void 0
|
|
7153
7430
|
};
|
|
7154
|
-
const
|
|
7155
|
-
let demoLoginActive = false;
|
|
7156
|
-
try {
|
|
7157
|
-
const plugin = await db.prepare("SELECT * FROM plugins WHERE id = ? AND status = ?").bind("demo-login-prefill", "active").first();
|
|
7158
|
-
demoLoginActive = !!plugin;
|
|
7159
|
-
} catch (error2) {
|
|
7160
|
-
}
|
|
7431
|
+
const demoLoginActive = isDemoModeActive();
|
|
7161
7432
|
return c.html(renderLoginPage(pageData, demoLoginActive));
|
|
7162
7433
|
});
|
|
7163
7434
|
authRoutes.get("/register", async (c) => {
|
|
@@ -7293,8 +7564,8 @@ authRoutes.post(
|
|
|
7293
7564
|
}
|
|
7294
7565
|
const { email, password } = validation.data;
|
|
7295
7566
|
const normalizedEmail = email.toLowerCase();
|
|
7296
|
-
const { createAuth } = await import('./config-
|
|
7297
|
-
const auth = createAuth(c.env);
|
|
7567
|
+
const { createAuth } = await import('./config-DOAWXZBG.js');
|
|
7568
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7298
7569
|
const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
|
|
7299
7570
|
method: "POST",
|
|
7300
7571
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin },
|
|
@@ -7304,12 +7575,14 @@ authRoutes.post(
|
|
|
7304
7575
|
if (!baRes.ok) {
|
|
7305
7576
|
return c.json({ error: "Invalid email or password" }, 401);
|
|
7306
7577
|
}
|
|
7307
|
-
|
|
7308
|
-
if (rawSetCookie) {
|
|
7309
|
-
c.res.headers.append("Set-Cookie", rawSetCookie);
|
|
7310
|
-
} else if (baRes.headers.getSetCookie) {
|
|
7578
|
+
if (baRes.headers.getSetCookie) {
|
|
7311
7579
|
for (const sc of baRes.headers.getSetCookie()) {
|
|
7312
|
-
c.
|
|
7580
|
+
c.header("Set-Cookie", sc, { append: true });
|
|
7581
|
+
}
|
|
7582
|
+
} else {
|
|
7583
|
+
const rawSetCookie = baRes.headers.get("set-cookie");
|
|
7584
|
+
if (rawSetCookie) {
|
|
7585
|
+
c.header("Set-Cookie", rawSetCookie, { append: true });
|
|
7313
7586
|
}
|
|
7314
7587
|
}
|
|
7315
7588
|
await setCsrfCookie(c);
|
|
@@ -7329,6 +7602,9 @@ authRoutes.post(
|
|
|
7329
7602
|
sameSite: "Strict",
|
|
7330
7603
|
maxAge: tokenTtl
|
|
7331
7604
|
});
|
|
7605
|
+
if (normalizedEmail === DEMO_EMAIL) {
|
|
7606
|
+
c.executionCtx?.waitUntil(trackDemoLogin(c.env.CACHE_KV));
|
|
7607
|
+
}
|
|
7332
7608
|
return c.json({
|
|
7333
7609
|
user: {
|
|
7334
7610
|
id: user.id,
|
|
@@ -7347,8 +7623,8 @@ authRoutes.post(
|
|
|
7347
7623
|
);
|
|
7348
7624
|
authRoutes.post("/logout", async (c) => {
|
|
7349
7625
|
try {
|
|
7350
|
-
const { createAuth } = await import('./config-
|
|
7351
|
-
const auth = createAuth(c.env);
|
|
7626
|
+
const { createAuth } = await import('./config-DOAWXZBG.js');
|
|
7627
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7352
7628
|
const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
|
|
7353
7629
|
method: "POST",
|
|
7354
7630
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin, "Cookie": c.req.header("Cookie") || "" },
|
|
@@ -7357,14 +7633,15 @@ authRoutes.post("/logout", async (c) => {
|
|
|
7357
7633
|
await auth.handler(baReq);
|
|
7358
7634
|
} catch {
|
|
7359
7635
|
}
|
|
7360
|
-
|
|
7636
|
+
const isSecure = new URL(c.req.url).protocol === "https:";
|
|
7637
|
+
setCookie(c, "better-auth.session_token", "", { httpOnly: true, sameSite: "Lax", path: "/", maxAge: 0, secure: isSecure });
|
|
7361
7638
|
clearCsrfCookie(c);
|
|
7362
7639
|
return c.json({ message: "Logged out successfully" });
|
|
7363
7640
|
});
|
|
7364
7641
|
authRoutes.get("/logout", async (c) => {
|
|
7365
7642
|
try {
|
|
7366
|
-
const { createAuth } = await import('./config-
|
|
7367
|
-
const auth = createAuth(c.env);
|
|
7643
|
+
const { createAuth } = await import('./config-DOAWXZBG.js');
|
|
7644
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7368
7645
|
const baReq = new Request(new URL("/auth/sign-out", c.req.url).href, {
|
|
7369
7646
|
method: "POST",
|
|
7370
7647
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin, "Cookie": c.req.header("Cookie") || "" },
|
|
@@ -7373,7 +7650,8 @@ authRoutes.get("/logout", async (c) => {
|
|
|
7373
7650
|
await auth.handler(baReq);
|
|
7374
7651
|
} catch {
|
|
7375
7652
|
}
|
|
7376
|
-
|
|
7653
|
+
const isSecure = new URL(c.req.url).protocol === "https:";
|
|
7654
|
+
setCookie(c, "better-auth.session_token", "", { httpOnly: true, sameSite: "Lax", path: "/", maxAge: 0, secure: isSecure });
|
|
7377
7655
|
clearCsrfCookie(c);
|
|
7378
7656
|
return c.redirect("/auth/login?message=You have been logged out successfully");
|
|
7379
7657
|
});
|
|
@@ -7570,8 +7848,8 @@ authRoutes.post(
|
|
|
7570
7848
|
</div>
|
|
7571
7849
|
`);
|
|
7572
7850
|
}
|
|
7573
|
-
const { createAuth } = await import('./config-
|
|
7574
|
-
const auth = createAuth(c.env);
|
|
7851
|
+
const { createAuth } = await import('./config-DOAWXZBG.js');
|
|
7852
|
+
const auth = createAuth(c.env, void 0, new URL(c.req.url).origin);
|
|
7575
7853
|
const baReq = new Request(new URL("/auth/sign-in/email", c.req.url).href, {
|
|
7576
7854
|
method: "POST",
|
|
7577
7855
|
headers: { "Content-Type": "application/json", "Origin": new URL(c.req.url).origin },
|
|
@@ -7585,17 +7863,27 @@ authRoutes.post(
|
|
|
7585
7863
|
</div>
|
|
7586
7864
|
`);
|
|
7587
7865
|
}
|
|
7588
|
-
|
|
7589
|
-
if (rawSetCookie) {
|
|
7590
|
-
c.res.headers.append("Set-Cookie", rawSetCookie);
|
|
7591
|
-
} else if (baRes.headers.getSetCookie) {
|
|
7866
|
+
if (baRes.headers.getSetCookie) {
|
|
7592
7867
|
for (const sc of baRes.headers.getSetCookie()) {
|
|
7593
|
-
c.
|
|
7868
|
+
c.header("Set-Cookie", sc, { append: true });
|
|
7869
|
+
}
|
|
7870
|
+
} else {
|
|
7871
|
+
const rawSetCookie = baRes.headers.get("set-cookie");
|
|
7872
|
+
if (rawSetCookie) {
|
|
7873
|
+
c.header("Set-Cookie", rawSetCookie, { append: true });
|
|
7594
7874
|
}
|
|
7595
7875
|
}
|
|
7596
7876
|
await setCsrfCookie(c);
|
|
7877
|
+
if (email === DEMO_EMAIL) {
|
|
7878
|
+
c.executionCtx?.waitUntil(trackDemoLogin(c.env.CACHE_KV));
|
|
7879
|
+
}
|
|
7597
7880
|
const rawRedirect = c.req.query("redirect");
|
|
7598
7881
|
const redirectUrl = rawRedirect && rawRedirect.startsWith("/") ? rawRedirect : "/admin/content";
|
|
7882
|
+
const isHtmx = c.req.header("HX-Request") === "true";
|
|
7883
|
+
if (isHtmx) {
|
|
7884
|
+
c.header("HX-Redirect", redirectUrl);
|
|
7885
|
+
return c.html(html`<div id="form-response"><p class="text-sm text-green-600">Login successful! Redirecting...</p></div>`);
|
|
7886
|
+
}
|
|
7599
7887
|
return c.html(html`
|
|
7600
7888
|
<div id="form-response">
|
|
7601
7889
|
<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 +9395,44 @@ function renderContentFormPage(data, opts) {
|
|
|
9107
9395
|
</a>
|
|
9108
9396
|
|
|
9109
9397
|
<div class="flex items-center gap-x-3">
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
>
|
|
9118
|
-
<
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9398
|
+
<button
|
|
9399
|
+
type="submit"
|
|
9400
|
+
form="content-form"
|
|
9401
|
+
name="action"
|
|
9402
|
+
value="save"
|
|
9403
|
+
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"
|
|
9404
|
+
>
|
|
9405
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
9406
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
|
9407
|
+
</svg>
|
|
9408
|
+
Save
|
|
9409
|
+
</button>
|
|
9410
|
+
|
|
9411
|
+
<button
|
|
9412
|
+
type="submit"
|
|
9413
|
+
form="content-form"
|
|
9414
|
+
name="action"
|
|
9415
|
+
value="save_and_close"
|
|
9416
|
+
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"
|
|
9417
|
+
>
|
|
9418
|
+
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
9419
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
|
9420
|
+
</svg>
|
|
9421
|
+
Save & close
|
|
9422
|
+
</button>
|
|
9124
9423
|
|
|
9125
|
-
${data.user?.role !== "viewer" ? `
|
|
9424
|
+
${data.user?.role !== "viewer" && data.versioningEnabled ? `
|
|
9126
9425
|
<button
|
|
9127
9426
|
type="submit"
|
|
9128
9427
|
form="content-form"
|
|
9129
9428
|
name="action"
|
|
9130
9429
|
value="save_and_publish"
|
|
9131
|
-
class="inline-flex items-center justify-center gap-x-1.5 rounded-lg
|
|
9430
|
+
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
9431
|
>
|
|
9133
9432
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
9134
9433
|
<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
9434
|
</svg>
|
|
9136
|
-
|
|
9435
|
+
Publish
|
|
9137
9436
|
</button>
|
|
9138
9437
|
` : ""}
|
|
9139
9438
|
</div>
|
|
@@ -12276,6 +12575,9 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
12276
12575
|
}), user?.userId);
|
|
12277
12576
|
const cache2 = getCacheService(CACHE_CONFIGS.content);
|
|
12278
12577
|
await cache2.invalidate(`content:list:${collectionId}:*`);
|
|
12578
|
+
const apiCacheCreate = getCacheService(CACHE_CONFIGS.api);
|
|
12579
|
+
await apiCacheCreate.invalidate("api:content-filtered:*");
|
|
12580
|
+
await apiCacheCreate.invalidate(`api:collection-content-filtered:${createDocType.id}:*`);
|
|
12279
12581
|
const redirectUrl2 = `/admin/content/${doc.rootId}/edit?success=Content created successfully!`;
|
|
12280
12582
|
return c.req.header("HX-Request") === "true" ? c.text("", 200, { "HX-Redirect": redirectUrl2 }) : c.redirect(redirectUrl2);
|
|
12281
12583
|
}
|
|
@@ -12394,8 +12696,28 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
12394
12696
|
if (status2 === "published") await svc.publish(newDraft.id, user?.userId);
|
|
12395
12697
|
else if (pub) await svc.unpublish(pub.id);
|
|
12396
12698
|
await getCacheService(CACHE_CONFIGS.content).invalidate(`content:list:*`);
|
|
12699
|
+
const apiCacheEdit = getCacheService(CACHE_CONFIGS.api);
|
|
12700
|
+
await apiCacheEdit.invalidate("api:content-filtered:*");
|
|
12701
|
+
await apiCacheEdit.invalidate(`api:collection-content-filtered:${docRowU.type_id}:*`);
|
|
12702
|
+
const isHTMX2 = c.req.header("HX-Request") === "true";
|
|
12703
|
+
const referrerParams = formData.get("referrer_params");
|
|
12704
|
+
const listUrl = referrerParams ? `/admin/content?${referrerParams}` : `/admin/content?collection=${dcoll.id}`;
|
|
12705
|
+
if (action === "save_and_close") {
|
|
12706
|
+
return isHTMX2 ? c.text("", 200, { "HX-Redirect": listUrl }) : c.redirect(listUrl);
|
|
12707
|
+
}
|
|
12708
|
+
if (isHTMX2) {
|
|
12709
|
+
const label = status2 === "published" ? "Content published successfully!" : "Content saved successfully!";
|
|
12710
|
+
return c.html(html`<div id="form-messages">
|
|
12711
|
+
<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">
|
|
12712
|
+
<svg class="h-5 w-5 text-green-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
|
|
12713
|
+
<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"/>
|
|
12714
|
+
</svg>
|
|
12715
|
+
<span class="text-sm font-medium text-green-800 dark:text-green-200">${label}</span>
|
|
12716
|
+
</div>
|
|
12717
|
+
</div>`);
|
|
12718
|
+
}
|
|
12397
12719
|
const redirectUrl2 = `/admin/content/${id}/edit?success=Content updated successfully!`;
|
|
12398
|
-
return c.
|
|
12720
|
+
return c.redirect(redirectUrl2);
|
|
12399
12721
|
}
|
|
12400
12722
|
}
|
|
12401
12723
|
return c.html(html`
|
|
@@ -12789,8 +13111,8 @@ adminContentRoutes.post("/bulk-action", async (c) => {
|
|
|
12789
13111
|
}
|
|
12790
13112
|
await cache.invalidate("content:list:*");
|
|
12791
13113
|
const apiCache = getCacheService(CACHE_CONFIGS.api);
|
|
12792
|
-
await apiCache.invalidate("content-filtered:*");
|
|
12793
|
-
await apiCache.invalidate("collection-content-filtered:*");
|
|
13114
|
+
await apiCache.invalidate("api:content-filtered:*");
|
|
13115
|
+
await apiCache.invalidate("api:collection-content-filtered:*");
|
|
12794
13116
|
return c.json({ success: true, count: ids.length });
|
|
12795
13117
|
} catch (error) {
|
|
12796
13118
|
console.error("Bulk action error:", error);
|
|
@@ -12808,6 +13130,9 @@ adminContentRoutes.delete("/:id", async (c) => {
|
|
|
12808
13130
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
12809
13131
|
await db.prepare("UPDATE documents SET deleted_at = ?, updated_at = ? WHERE root_id = ? AND tenant_id = ?").bind(now2, now2, id, tenantId).run();
|
|
12810
13132
|
await getCacheService(CACHE_CONFIGS.content).invalidate("content:list:*");
|
|
13133
|
+
const apiCacheDel = getCacheService(CACHE_CONFIGS.api);
|
|
13134
|
+
await apiCacheDel.invalidate("api:content-filtered:*");
|
|
13135
|
+
await apiCacheDel.invalidate(`api:collection-content-filtered:${docDel.type_id}:*`);
|
|
12811
13136
|
return c.html(`
|
|
12812
13137
|
<div id="content-list" hx-get="/admin/content?model=${docDel.type_id}" hx-trigger="load" hx-swap="outerHTML">
|
|
12813
13138
|
<div class="flex items-center justify-center p-8"><span class="text-zinc-500">Deleting\u2026</span></div>
|
|
@@ -13103,7 +13428,7 @@ adminContentRoutes.post("/documents/:typeId/:documentId/delete", async (c) => {
|
|
|
13103
13428
|
adminContentRoutes.get("/documents/:typeId/:rootId/versions", async (c) => {
|
|
13104
13429
|
const { typeId, rootId } = c.req.param();
|
|
13105
13430
|
const db = c.env.DB;
|
|
13106
|
-
const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-
|
|
13431
|
+
const { renderVersionHistoryFragment } = await import('./admin-documents-form.template-JWV5GITD.js');
|
|
13107
13432
|
const registry = new DocumentTypeRegistry(db);
|
|
13108
13433
|
const docType = await registry.findById(typeId);
|
|
13109
13434
|
if (!docType) return c.html("<div>Unknown type.</div>", 404);
|
|
@@ -15358,12 +15683,10 @@ var LANGUAGES = [
|
|
|
15358
15683
|
{ value: "ko", label: "Korean" },
|
|
15359
15684
|
{ value: "zh", label: "Chinese" }
|
|
15360
15685
|
];
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
{ value:
|
|
15364
|
-
|
|
15365
|
-
{ value: "viewer", label: "Viewer" }
|
|
15366
|
-
];
|
|
15686
|
+
async function fetchRoleOptions(db) {
|
|
15687
|
+
const roles = await new RbacService(db).getRoles();
|
|
15688
|
+
return roles.map((r) => ({ value: r.id, label: r.display_name }));
|
|
15689
|
+
}
|
|
15367
15690
|
userRoutes.get("/profile", async (c) => {
|
|
15368
15691
|
const user = c.get("user");
|
|
15369
15692
|
const db = c.env.DB;
|
|
@@ -15812,8 +16135,9 @@ userRoutes.get("/users/new", async (c) => {
|
|
|
15812
16135
|
const regFieldNames = profileConfig?.registrationFields ?? [];
|
|
15813
16136
|
const regFields = profileConfig && upRow ? regFieldNames.length > 0 ? profileConfig.fields.filter((f) => regFieldNames.includes(f.name)) : profileConfig.fields : [];
|
|
15814
16137
|
const registrationFieldsHtml = regFields.length > 0 ? renderCustomProfileSection({ fields: regFields }, {}) : void 0;
|
|
16138
|
+
const roles = await fetchRoleOptions(db);
|
|
15815
16139
|
const pageData = {
|
|
15816
|
-
roles
|
|
16140
|
+
roles,
|
|
15817
16141
|
registrationFieldsHtml,
|
|
15818
16142
|
user: {
|
|
15819
16143
|
name: user.email.split("@")[0] || user.email,
|
|
@@ -15841,9 +16165,9 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
15841
16165
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
15842
16166
|
const phone = sanitizeInput(formData.get("phone")?.toString()) || null;
|
|
15843
16167
|
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 : "
|
|
16168
|
+
const roleInput = formData.get("role")?.toString() || "";
|
|
16169
|
+
const validRoles = (await new RbacService(db).getRoles()).map((r) => r.id);
|
|
16170
|
+
const role = validRoles.includes(roleInput) ? roleInput : validRoles[0] ?? "admin";
|
|
15847
16171
|
const password = formData.get("password")?.toString() || "";
|
|
15848
16172
|
const confirmPassword = formData.get("confirm_password")?.toString() || "";
|
|
15849
16173
|
const isActive = formData.get("is_active") === "1";
|
|
@@ -16065,9 +16389,10 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
16065
16389
|
}
|
|
16066
16390
|
const queryMessage = c.req.query("message");
|
|
16067
16391
|
const queryType = c.req.query("type") === "error" ? "error" : "success";
|
|
16392
|
+
const roles = await fetchRoleOptions(db);
|
|
16068
16393
|
const pageData = {
|
|
16069
16394
|
userToEdit: editData,
|
|
16070
|
-
roles
|
|
16395
|
+
roles,
|
|
16071
16396
|
hasProfilePlugin: profileConfig !== null && upRow !== null,
|
|
16072
16397
|
customProfileFieldsHtml,
|
|
16073
16398
|
tenantMemberships,
|
|
@@ -16099,9 +16424,9 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
16099
16424
|
const lastName = sanitizeInput(formData.get("last_name")?.toString());
|
|
16100
16425
|
const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
|
|
16101
16426
|
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 : "
|
|
16427
|
+
const roleInput = formData.get("role")?.toString() || "";
|
|
16428
|
+
const validRoles = (await new RbacService(db).getRoles()).map((r) => r.id);
|
|
16429
|
+
const role = validRoles.includes(roleInput) ? roleInput : validRoles[0] ?? "admin";
|
|
16105
16430
|
const isActive = formData.get("is_active") === "1";
|
|
16106
16431
|
const emailVerified = formData.get("email_verified") === "1";
|
|
16107
16432
|
const newPassword = formData.get("new_password")?.toString() || "";
|
|
@@ -18688,15 +19013,6 @@ function renderPluginsListPage(data) {
|
|
|
18688
19013
|
</div>
|
|
18689
19014
|
|
|
18690
19015
|
<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
19016
|
<button
|
|
18701
19017
|
onclick="location.reload()"
|
|
18702
19018
|
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 +19176,6 @@ function renderPluginsListPage(data) {
|
|
|
18860
19176
|
.forEach(cb => params.append('status', cb.value));
|
|
18861
19177
|
const search = document.getElementById('search-input').value;
|
|
18862
19178
|
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
19179
|
const url = new URL(window.location.href);
|
|
18866
19180
|
url.search = params.toString();
|
|
18867
19181
|
history.replaceState(null, '', url.toString());
|
|
@@ -18879,8 +19193,6 @@ function renderPluginsListPage(data) {
|
|
|
18879
19193
|
});
|
|
18880
19194
|
const search = params.get('search');
|
|
18881
19195
|
if (search) document.getElementById('search-input').value = search;
|
|
18882
|
-
const sort = params.get('sort');
|
|
18883
|
-
if (sort) document.getElementById('sort-filter').value = sort;
|
|
18884
19196
|
if (params.toString()) filterAndSortPlugins();
|
|
18885
19197
|
}
|
|
18886
19198
|
|
|
@@ -18896,7 +19208,6 @@ function renderPluginsListPage(data) {
|
|
|
18896
19208
|
.map(cb => cb.value.toLowerCase());
|
|
18897
19209
|
|
|
18898
19210
|
const searchInput = document.getElementById('search-input').value.toLowerCase();
|
|
18899
|
-
const sortValue = document.getElementById('sort-filter').value;
|
|
18900
19211
|
|
|
18901
19212
|
const pluginsGrid = document.getElementById('plugins-grid');
|
|
18902
19213
|
const pluginCards = Array.from(pluginsGrid.querySelectorAll('.plugin-card'));
|
|
@@ -18920,30 +19231,6 @@ function renderPluginsListPage(data) {
|
|
|
18920
19231
|
return true;
|
|
18921
19232
|
});
|
|
18922
19233
|
|
|
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
19234
|
// Re-append
|
|
18948
19235
|
pluginCards.forEach(card => card.style.display = 'none'); // Hide all first
|
|
18949
19236
|
|
|
@@ -19378,7 +19665,8 @@ function escapeHtmlAttr(value) {
|
|
|
19378
19665
|
function renderPluginSettingsPage(data) {
|
|
19379
19666
|
const { plugin, activity = [], user, settingsTabData } = data;
|
|
19380
19667
|
const pluginDef = getPluginDefinition(plugin.id || plugin.name);
|
|
19381
|
-
const
|
|
19668
|
+
const pluginId = plugin.id || plugin.name;
|
|
19669
|
+
const hasUserSettings = pluginDef?.settingsTabContent != null || Object.keys(plugin.settings || {}).some((k) => !k.startsWith("_")) || hasCustomSettingsComponent(pluginId);
|
|
19382
19670
|
const defaultTab = hasUserSettings ? "settings" : "info";
|
|
19383
19671
|
const pageContent = `
|
|
19384
19672
|
<div class="w-full px-4 sm:px-6 lg:px-8 py-6">
|
|
@@ -19543,9 +19831,28 @@ function renderPluginSettingsPage(data) {
|
|
|
19543
19831
|
const form = document.getElementById('settings-form');
|
|
19544
19832
|
const formData = new FormData(form);
|
|
19545
19833
|
const isAuthPlugin = '${plugin.id}' === 'core-auth';
|
|
19834
|
+
const isOAuthPlugin = '${plugin.id}' === 'oauth-providers';
|
|
19546
19835
|
let settings = {};
|
|
19547
19836
|
|
|
19548
|
-
if (
|
|
19837
|
+
if (isOAuthPlugin) {
|
|
19838
|
+
// Build nested provider settings structure
|
|
19839
|
+
const getVal = id => (document.getElementById(id) || {}).value || '';
|
|
19840
|
+
const getChecked = id => !!(document.getElementById(id) || {}).checked;
|
|
19841
|
+
settings = {
|
|
19842
|
+
providers: {
|
|
19843
|
+
github: {
|
|
19844
|
+
clientId: getVal('oauth_github_clientId'),
|
|
19845
|
+
clientSecret: getVal('oauth_github_clientSecret'),
|
|
19846
|
+
enabled: getChecked('oauth_github_enabled'),
|
|
19847
|
+
},
|
|
19848
|
+
google: {
|
|
19849
|
+
clientId: getVal('oauth_google_clientId'),
|
|
19850
|
+
clientSecret: getVal('oauth_google_clientSecret'),
|
|
19851
|
+
enabled: getChecked('oauth_google_enabled'),
|
|
19852
|
+
},
|
|
19853
|
+
}
|
|
19854
|
+
};
|
|
19855
|
+
} else if (isAuthPlugin) {
|
|
19549
19856
|
// Handle nested auth settings structure
|
|
19550
19857
|
settings = {
|
|
19551
19858
|
requiredFields: {},
|
|
@@ -20033,7 +20340,8 @@ function formatTimestamp(timestamp) {
|
|
|
20033
20340
|
}
|
|
20034
20341
|
var pluginSettingsComponents = {
|
|
20035
20342
|
"otp-login": renderOTPLoginSettingsContent,
|
|
20036
|
-
"email": renderEmailSettingsContent
|
|
20343
|
+
"email": renderEmailSettingsContent,
|
|
20344
|
+
"oauth-providers": renderOAuthProvidersSettingsContent
|
|
20037
20345
|
};
|
|
20038
20346
|
function renderOTPLoginSettingsContent(plugin, settings) {
|
|
20039
20347
|
const siteName = settings.siteName || "SonicJS";
|
|
@@ -20756,6 +21064,62 @@ defineUserProfile({
|
|
|
20756
21064
|
</div>
|
|
20757
21065
|
`;
|
|
20758
21066
|
}
|
|
21067
|
+
function renderOAuthProvidersSettingsContent(plugin, settings) {
|
|
21068
|
+
const providers = settings.providers || {};
|
|
21069
|
+
const gh = providers.github || {};
|
|
21070
|
+
const gg = providers.google || {};
|
|
21071
|
+
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";
|
|
21072
|
+
return `
|
|
21073
|
+
<div class="space-y-6">
|
|
21074
|
+
<form id="settings-form">
|
|
21075
|
+
<div class="backdrop-blur-md bg-white/5 rounded-xl border border-white/10 p-6 space-y-6">
|
|
21076
|
+
<h3 class="text-lg font-semibold text-white">GitHub OAuth</h3>
|
|
21077
|
+
<div>
|
|
21078
|
+
<label for="oauth_github_clientId" class="block text-sm font-medium text-gray-300 mb-2">Client ID</label>
|
|
21079
|
+
<input type="text" id="oauth_github_clientId" name="oauth_github_clientId"
|
|
21080
|
+
value="${escapeHtmlAttr(gh.clientId || "")}"
|
|
21081
|
+
placeholder="GitHub OAuth App Client ID" class="${inputClass}" />
|
|
21082
|
+
</div>
|
|
21083
|
+
<div>
|
|
21084
|
+
<label for="oauth_github_clientSecret" class="block text-sm font-medium text-gray-300 mb-2">Client Secret</label>
|
|
21085
|
+
<input type="password" id="oauth_github_clientSecret" name="oauth_github_clientSecret"
|
|
21086
|
+
value="${escapeHtmlAttr(gh.clientSecret || "")}"
|
|
21087
|
+
placeholder="GitHub OAuth App Client Secret" class="${inputClass}" />
|
|
21088
|
+
</div>
|
|
21089
|
+
<div class="flex items-center gap-3">
|
|
21090
|
+
<input type="checkbox" id="oauth_github_enabled" name="oauth_github_enabled"
|
|
21091
|
+
${gh.enabled ? "checked" : ""} class="w-4 h-4 rounded border-white/10" />
|
|
21092
|
+
<label for="oauth_github_enabled" class="text-sm text-gray-300">Enable GitHub login</label>
|
|
21093
|
+
</div>
|
|
21094
|
+
</div>
|
|
21095
|
+
|
|
21096
|
+
<div class="backdrop-blur-md bg-white/5 rounded-xl border border-white/10 p-6 space-y-6 mt-6">
|
|
21097
|
+
<h3 class="text-lg font-semibold text-white">Google OAuth</h3>
|
|
21098
|
+
<div>
|
|
21099
|
+
<label for="oauth_google_clientId" class="block text-sm font-medium text-gray-300 mb-2">Client ID</label>
|
|
21100
|
+
<input type="text" id="oauth_google_clientId" name="oauth_google_clientId"
|
|
21101
|
+
value="${escapeHtmlAttr(gg.clientId || "")}"
|
|
21102
|
+
placeholder="Google OAuth Client ID" class="${inputClass}" />
|
|
21103
|
+
</div>
|
|
21104
|
+
<div>
|
|
21105
|
+
<label for="oauth_google_clientSecret" class="block text-sm font-medium text-gray-300 mb-2">Client Secret</label>
|
|
21106
|
+
<input type="password" id="oauth_google_clientSecret" name="oauth_google_clientSecret"
|
|
21107
|
+
value="${escapeHtmlAttr(gg.clientSecret || "")}"
|
|
21108
|
+
placeholder="Google OAuth Client Secret" class="${inputClass}" />
|
|
21109
|
+
</div>
|
|
21110
|
+
<div class="flex items-center gap-3">
|
|
21111
|
+
<input type="checkbox" id="oauth_google_enabled" name="oauth_google_enabled"
|
|
21112
|
+
${gg.enabled ? "checked" : ""} class="w-4 h-4 rounded border-white/10" />
|
|
21113
|
+
<label for="oauth_google_enabled" class="text-sm text-gray-300">Enable Google login</label>
|
|
21114
|
+
</div>
|
|
21115
|
+
</div>
|
|
21116
|
+
</form>
|
|
21117
|
+
</div>
|
|
21118
|
+
`;
|
|
21119
|
+
}
|
|
21120
|
+
function hasCustomSettingsComponent(pluginId) {
|
|
21121
|
+
return pluginId in pluginSettingsComponents;
|
|
21122
|
+
}
|
|
20759
21123
|
|
|
20760
21124
|
// src/plugins/core-plugins/menu-plugin/services/menu-reconcile.ts
|
|
20761
21125
|
async function upsertPluginRow(db, slug, entry, visible) {
|
|
@@ -27116,6 +27480,6 @@ var ROUTES_INFO = {
|
|
|
27116
27480
|
reference: "https://github.com/sonicjs/sonicjs"
|
|
27117
27481
|
};
|
|
27118
27482
|
|
|
27119
|
-
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-
|
|
27483
|
+
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, setDemoModeActive, setEmailService, setVariablesCache, swrStats, test_cleanup_default, updateDocumentSchema, userProfilesPlugin, userRoutes };
|
|
27484
|
+
//# sourceMappingURL=chunk-OJFIQGZB.js.map
|
|
27485
|
+
//# sourceMappingURL=chunk-OJFIQGZB.js.map
|