@sonicjs-cms/core 2.19.0 → 3.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -52
- package/dist/admin-documents-form.template-DDSH6ROU.js +6 -0
- package/dist/{admin-layout-catalyst.template-UMTIN66R.js.map → admin-documents-form.template-DDSH6ROU.js.map} +1 -1
- package/dist/admin-documents-form.template-LSZKGA5J.cjs +19 -0
- package/dist/{admin-layout-catalyst.template-HFD37TY5.cjs.map → admin-documents-form.template-LSZKGA5J.cjs.map} +1 -1
- package/dist/{filter-bar.template-DlVYMk-T.d.cts → admin-layout-catalyst.template-DrwDUfsE.d.cts} +25 -1
- package/dist/{filter-bar.template-DlVYMk-T.d.ts → admin-layout-catalyst.template-DrwDUfsE.d.ts} +25 -1
- package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs +21 -0
- package/dist/admin-layout-catalyst.template-KDHKVLXR.cjs.map +1 -0
- package/dist/admin-layout-catalyst.template-YQ4EMF2J.js +7 -0
- package/dist/admin-layout-catalyst.template-YQ4EMF2J.js.map +1 -0
- package/dist/app-Bo0X1OWX.d.ts +1268 -0
- package/dist/app-Do66yCcV.d.cts +1268 -0
- package/dist/cache-DDARE4QE.js +4 -0
- package/dist/cache-DDARE4QE.js.map +1 -0
- package/dist/cache-LVYS4BPL.cjs +33 -0
- package/dist/cache-LVYS4BPL.cjs.map +1 -0
- package/dist/chunk-2CB4KY7I.cjs +771 -0
- package/dist/chunk-2CB4KY7I.cjs.map +1 -0
- package/dist/{chunk-ABB34XUS.cjs → chunk-3KYKEXV7.cjs} +667 -19
- package/dist/chunk-3KYKEXV7.cjs.map +1 -0
- package/dist/chunk-4BTBSXMR.cjs +912 -0
- package/dist/chunk-4BTBSXMR.cjs.map +1 -0
- package/dist/{chunk-55RDMDOP.js → chunk-5V62WT6M.js} +181 -57
- package/dist/chunk-5V62WT6M.js.map +1 -0
- package/dist/{chunk-OCL3HMEG.js → chunk-6OC6MF3C.js} +7004 -9807
- package/dist/chunk-6OC6MF3C.js.map +1 -0
- package/dist/chunk-AI663NBO.js +821 -0
- package/dist/chunk-AI663NBO.js.map +1 -0
- package/dist/chunk-ALDRXTUO.js +273 -0
- package/dist/chunk-ALDRXTUO.js.map +1 -0
- package/dist/{chunk-TFNTM3OA.js → chunk-ATUPB6MN.js} +645 -15
- package/dist/chunk-ATUPB6MN.js.map +1 -0
- package/dist/chunk-BLMTL57B.js +767 -0
- package/dist/chunk-BLMTL57B.js.map +1 -0
- package/dist/{chunk-4ZSNJDLS.cjs → chunk-CRGUD4KC.cjs} +9 -9
- package/dist/chunk-CRGUD4KC.cjs.map +1 -0
- package/dist/chunk-F67UK75A.cjs +158 -0
- package/dist/chunk-F67UK75A.cjs.map +1 -0
- package/dist/chunk-GCDZZNIN.js +192 -0
- package/dist/chunk-GCDZZNIN.js.map +1 -0
- package/dist/chunk-HIKBY7MS.cjs +70 -0
- package/dist/chunk-HIKBY7MS.cjs.map +1 -0
- package/dist/{chunk-4NPCDK6B.js → chunk-IDCZBF35.js} +557 -90
- package/dist/chunk-IDCZBF35.js.map +1 -0
- package/dist/chunk-IESEVHXL.js +66 -0
- package/dist/chunk-IESEVHXL.js.map +1 -0
- package/dist/chunk-IGADDMXH.js +387 -0
- package/dist/chunk-IGADDMXH.js.map +1 -0
- package/dist/chunk-IHTXB7AT.cjs +276 -0
- package/dist/chunk-IHTXB7AT.cjs.map +1 -0
- package/dist/chunk-IVPRUGTY.js +242 -0
- package/dist/chunk-IVPRUGTY.js.map +1 -0
- package/dist/{chunk-JZVHLLSI.cjs → chunk-IXUHXTHW.cjs} +2 -151
- package/dist/chunk-IXUHXTHW.cjs.map +1 -0
- package/dist/chunk-J6JTWD2A.cjs +100 -0
- package/dist/chunk-J6JTWD2A.cjs.map +1 -0
- package/dist/chunk-JEQ7FLOD.cjs +199 -0
- package/dist/chunk-JEQ7FLOD.cjs.map +1 -0
- package/dist/{chunk-ON5ZMSU4.js → chunk-JQISFW6U.js} +3 -3
- package/dist/chunk-JQISFW6U.js.map +1 -0
- package/dist/chunk-K25XHMM3.js +566 -0
- package/dist/chunk-K25XHMM3.js.map +1 -0
- package/dist/{chunk-UYJ6TJHX.cjs → chunk-K623Q6WD.cjs} +181 -56
- package/dist/chunk-K623Q6WD.cjs.map +1 -0
- package/dist/{chunk-7A4CB7T3.cjs → chunk-MUNO67TT.cjs} +561 -91
- package/dist/chunk-MUNO67TT.cjs.map +1 -0
- package/dist/chunk-N32OWET6.cjs +327 -0
- package/dist/chunk-N32OWET6.cjs.map +1 -0
- package/dist/chunk-NUKJ54GA.cjs +245 -0
- package/dist/chunk-NUKJ54GA.cjs.map +1 -0
- package/dist/{chunk-XWIA3HVX.js → chunk-OBA2RYZN.js} +6 -1249
- package/dist/chunk-OBA2RYZN.js.map +1 -0
- package/dist/chunk-PMGOBS6X.cjs +408 -0
- package/dist/chunk-PMGOBS6X.cjs.map +1 -0
- package/dist/{chunk-OHYBNCVL.cjs → chunk-PXNTCCPE.cjs} +10 -1256
- package/dist/chunk-PXNTCCPE.cjs.map +1 -0
- package/dist/chunk-PYVFXCSD.js +1828 -0
- package/dist/chunk-PYVFXCSD.js.map +1 -0
- package/dist/{chunk-BU7SFHGP.js → chunk-QZGABF2M.js} +3 -149
- package/dist/chunk-QZGABF2M.js.map +1 -0
- package/dist/{chunk-E4YFJBM2.cjs → chunk-R4ILO3W6.cjs} +876 -829
- package/dist/chunk-R4ILO3W6.cjs.map +1 -0
- package/dist/chunk-RMRJGMDE.js +323 -0
- package/dist/chunk-RMRJGMDE.js.map +1 -0
- package/dist/chunk-RNZFGN4R.js +88 -0
- package/dist/chunk-RNZFGN4R.js.map +1 -0
- package/dist/chunk-RQ6N3FTV.js +900 -0
- package/dist/chunk-RQ6N3FTV.js.map +1 -0
- package/dist/{chunk-R4FOLLFB.cjs → chunk-TO6EY4P7.cjs} +8730 -11520
- package/dist/chunk-TO6EY4P7.cjs.map +1 -0
- package/dist/chunk-V464XBYS.js +154 -0
- package/dist/chunk-V464XBYS.js.map +1 -0
- package/dist/chunk-YA3TJ65D.cjs +575 -0
- package/dist/chunk-YA3TJ65D.cjs.map +1 -0
- package/dist/chunk-YP7GW2G5.cjs +866 -0
- package/dist/chunk-YP7GW2G5.cjs.map +1 -0
- package/dist/{collection-config-B4PG-AaF.d.cts → collection-config-JgHOpFCG.d.cts} +30 -2
- package/dist/{collection-config-B4PG-AaF.d.ts → collection-config-JgHOpFCG.d.ts} +30 -2
- package/dist/config-HFXANXCC.js +6 -0
- package/dist/config-HFXANXCC.js.map +1 -0
- package/dist/config-ON6FNMYX.cjs +19 -0
- package/dist/config-ON6FNMYX.cjs.map +1 -0
- package/dist/define-plugin-BzNHc1ZI.d.ts +1321 -0
- package/dist/define-plugin-IWDKYaVm.d.cts +1321 -0
- package/dist/document-projection-TDWRJX3Z.cjs +13 -0
- package/dist/document-projection-TDWRJX3Z.cjs.map +1 -0
- package/dist/document-projection-YYMC6I4U.js +4 -0
- package/dist/document-projection-YYMC6I4U.js.map +1 -0
- package/dist/index.cjs +13736 -4326
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +331 -493
- package/dist/index.d.ts +331 -493
- package/dist/index.js +13455 -4067
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +38 -32
- package/dist/middleware.d.cts +50 -7
- package/dist/middleware.d.ts +50 -7
- package/dist/middleware.js +9 -3
- package/dist/migrations-2XHQEGOQ.cjs +13 -0
- package/dist/{migrations-566IIPS2.cjs.map → migrations-2XHQEGOQ.cjs.map} +1 -1
- package/dist/migrations-PE3CDVSM.js +4 -0
- package/dist/{migrations-H5IXZNCO.js.map → migrations-PE3CDVSM.js.map} +1 -1
- package/dist/{plugin-bootstrap-DfVerYV4.d.cts → plugin-bootstrap-B8ThJU21.d.cts} +4315 -1661
- package/dist/{plugin-bootstrap-P_ciLp_C.d.ts → plugin-bootstrap-qu8hJgUt.d.ts} +4315 -1661
- package/dist/plugins.cjs +171 -12
- package/dist/plugins.d.cts +36 -2
- package/dist/plugins.d.ts +36 -2
- package/dist/plugins.js +5 -2
- package/dist/rbac-O73MFKDA.js +5 -0
- package/dist/rbac-O73MFKDA.js.map +1 -0
- package/dist/rbac-VONLJJKB.cjs +14 -0
- package/dist/rbac-VONLJJKB.cjs.map +1 -0
- package/dist/routes.cjs +42 -46
- package/dist/routes.d.cts +56 -146
- package/dist/routes.d.ts +56 -146
- package/dist/routes.js +18 -10
- package/dist/services.cjs +43 -76
- package/dist/services.d.cts +93 -55
- package/dist/services.d.ts +93 -55
- package/dist/services.js +6 -3
- package/dist/{telemetry-B9vIV4wh.d.cts → telemetry-Cku1ax74.d.cts} +1 -1
- package/dist/{telemetry-B9vIV4wh.d.ts → telemetry-Cku1ax74.d.ts} +1 -1
- package/dist/templates.cjs +17 -29
- package/dist/templates.d.cts +2 -89
- package/dist/templates.d.ts +2 -89
- package/dist/templates.js +3 -3
- package/dist/types-Dea1eNxU.d.cts +286 -0
- package/dist/types-Dea1eNxU.d.ts +286 -0
- package/dist/types.d.cts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/utils.cjs +21 -20
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +3 -2
- package/migrations/0001_core.sql +184 -0
- package/migrations/0002_documents.sql +163 -0
- package/package.json +12 -7
- package/dist/admin-layout-catalyst.template-HFD37TY5.cjs +0 -17
- package/dist/admin-layout-catalyst.template-UMTIN66R.js +0 -7
- package/dist/app-C9esKLmh.d.cts +0 -112
- package/dist/app-C9esKLmh.d.ts +0 -112
- package/dist/chunk-4NPCDK6B.js.map +0 -1
- package/dist/chunk-4ZSNJDLS.cjs.map +0 -1
- package/dist/chunk-55RDMDOP.js.map +0 -1
- package/dist/chunk-635JAMSE.cjs +0 -653
- package/dist/chunk-635JAMSE.cjs.map +0 -1
- package/dist/chunk-7A4CB7T3.cjs.map +0 -1
- package/dist/chunk-ABB34XUS.cjs.map +0 -1
- package/dist/chunk-BU7SFHGP.js.map +0 -1
- package/dist/chunk-E4YFJBM2.cjs.map +0 -1
- package/dist/chunk-EXNEW5US.js +0 -648
- package/dist/chunk-EXNEW5US.js.map +0 -1
- package/dist/chunk-JZV22DEV.js +0 -1783
- package/dist/chunk-JZV22DEV.js.map +0 -1
- package/dist/chunk-JZVHLLSI.cjs.map +0 -1
- package/dist/chunk-OCL3HMEG.js.map +0 -1
- package/dist/chunk-OHYBNCVL.cjs.map +0 -1
- package/dist/chunk-ON5ZMSU4.js.map +0 -1
- package/dist/chunk-QFWHAFEO.js +0 -1843
- package/dist/chunk-QFWHAFEO.js.map +0 -1
- package/dist/chunk-R4FOLLFB.cjs.map +0 -1
- package/dist/chunk-RLMUFFUD.cjs +0 -2219
- package/dist/chunk-RLMUFFUD.cjs.map +0 -1
- package/dist/chunk-TFNTM3OA.js.map +0 -1
- package/dist/chunk-UYJ6TJHX.cjs.map +0 -1
- package/dist/chunk-WAEQXGCX.cjs +0 -1898
- package/dist/chunk-WAEQXGCX.cjs.map +0 -1
- package/dist/chunk-XWIA3HVX.js.map +0 -1
- package/dist/chunk-ZYAYUIZE.js +0 -2217
- package/dist/chunk-ZYAYUIZE.js.map +0 -1
- package/dist/migrations-566IIPS2.cjs +0 -13
- package/dist/migrations-H5IXZNCO.js +0 -4
- package/dist/plugin-manager-BoM3Q7o7.d.cts +0 -328
- package/dist/plugin-manager-Efx9RyDX.d.ts +0 -328
- package/migrations/001_initial_schema.sql +0 -170
- package/migrations/002_faq_plugin.sql +0 -86
- package/migrations/003_stage5_enhancements.sql +0 -121
- package/migrations/004_stage6_user_management.sql +0 -183
- package/migrations/005_stage7_workflow_automation.sql +0 -294
- package/migrations/006_plugin_system.sql +0 -155
- package/migrations/007_demo_login_plugin.sql +0 -23
- package/migrations/008_fix_slug_validation.sql +0 -22
- package/migrations/009_system_logging.sql +0 -57
- package/migrations/011_config_managed_collections.sql +0 -15
- package/migrations/012_testimonials_plugin.sql +0 -80
- package/migrations/013_code_examples_plugin.sql +0 -177
- package/migrations/014_fix_plugin_registry.sql +0 -88
- package/migrations/015_add_remaining_plugins.sql +0 -89
- package/migrations/016_remove_duplicate_cache_plugin.sql +0 -17
- package/migrations/017_auth_configurable_fields.sql +0 -49
- package/migrations/018_settings_table.sql +0 -23
- package/migrations/019_remove_blog_posts_collection.sql +0 -15
- package/migrations/020_add_email_plugin.sql +0 -22
- package/migrations/021_add_magic_link_auth_plugin.sql +0 -42
- package/migrations/022_add_tinymce_plugin.sql +0 -25
- package/migrations/023_add_easy_mdx_plugin.sql +0 -25
- package/migrations/024_add_quill_editor_plugin.sql +0 -25
- package/migrations/025_add_easymde_plugin.sql +0 -25
- package/migrations/026_add_otp_login.sql +0 -42
- package/migrations/027_fix_slug_field_type.sql +0 -18
- package/migrations/028_fix_slug_field_type_in_schemas.sql +0 -30
- package/migrations/029_add_forms_system.sql +0 -184
- package/migrations/030_add_turnstile_to_forms.sql +0 -14
- package/migrations/031_ai_search_plugin.sql +0 -45
- package/migrations/032_user_profiles.sql +0 -37
- package/migrations/033_form_content_integration.sql +0 -19
- package/migrations/034_security_audit_plugin.sql +0 -27
- package/migrations/035_user_profiles_data_column.sql +0 -16
- package/migrations/036_analytics_events.sql +0 -22
|
@@ -1,6 +1,126 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunkP3XDZL6Q_cjs = require('./chunk-P3XDZL6Q.cjs');
|
|
4
|
+
var chunkMNWKYY5E_cjs = require('./chunk-MNWKYY5E.cjs');
|
|
5
|
+
var cookie = require('hono/cookie');
|
|
6
|
+
|
|
7
|
+
// src/services/collection-registry.ts
|
|
8
|
+
var CollectionRegistry = class {
|
|
9
|
+
byName = /* @__PURE__ */ new Map();
|
|
10
|
+
bySlug = /* @__PURE__ */ new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Replace the registry contents with the given configs. Idempotent —
|
|
13
|
+
* calling with the same configs twice yields the same state.
|
|
14
|
+
*/
|
|
15
|
+
register(configs) {
|
|
16
|
+
this.byName.clear();
|
|
17
|
+
this.bySlug.clear();
|
|
18
|
+
for (const config of configs) {
|
|
19
|
+
if (!config.name) continue;
|
|
20
|
+
const record = {
|
|
21
|
+
...config,
|
|
22
|
+
id: config.name,
|
|
23
|
+
slug: config.slug ?? config.name.replace(/_/g, "-"),
|
|
24
|
+
managed: config.managed !== void 0 ? config.managed : true,
|
|
25
|
+
isActive: config.isActive !== void 0 ? config.isActive : true
|
|
26
|
+
};
|
|
27
|
+
this.byName.set(record.name, record);
|
|
28
|
+
this.bySlug.set(record.slug, record);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** All registered collections (including inactive). */
|
|
32
|
+
list() {
|
|
33
|
+
return Array.from(this.byName.values());
|
|
34
|
+
}
|
|
35
|
+
/** Active collections only. */
|
|
36
|
+
listActive() {
|
|
37
|
+
return this.list().filter((c) => c.isActive !== false);
|
|
38
|
+
}
|
|
39
|
+
getByName(name) {
|
|
40
|
+
return this.byName.get(name);
|
|
41
|
+
}
|
|
42
|
+
/** For code-defined collections, id === name. */
|
|
43
|
+
getById(id) {
|
|
44
|
+
return this.byName.get(id);
|
|
45
|
+
}
|
|
46
|
+
/** Look up by the URL slug (set in CollectionConfig.slug). Falls back to getByName if needed. */
|
|
47
|
+
getBySlug(slug) {
|
|
48
|
+
return this.bySlug.get(slug);
|
|
49
|
+
}
|
|
50
|
+
/** Resolve a path segment to a record — tries slug first, then name. */
|
|
51
|
+
getBySlugOrName(slugOrName) {
|
|
52
|
+
return this.bySlug.get(slugOrName) ?? this.byName.get(slugOrName);
|
|
53
|
+
}
|
|
54
|
+
isActive(name) {
|
|
55
|
+
const record = this.byName.get(name);
|
|
56
|
+
return record?.isActive !== false && record !== void 0;
|
|
57
|
+
}
|
|
58
|
+
size() {
|
|
59
|
+
return this.byName.size;
|
|
60
|
+
}
|
|
61
|
+
/** Test helper — wipe state. */
|
|
62
|
+
clear() {
|
|
63
|
+
this.byName.clear();
|
|
64
|
+
this.bySlug.clear();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function collectionRecordToRow(record) {
|
|
68
|
+
return {
|
|
69
|
+
id: record.id,
|
|
70
|
+
name: record.name,
|
|
71
|
+
display_name: record.displayName,
|
|
72
|
+
description: record.description ?? null,
|
|
73
|
+
schema: record.schema,
|
|
74
|
+
is_active: record.isActive === false ? 0 : 1,
|
|
75
|
+
managed: record.managed === false ? 0 : 1,
|
|
76
|
+
source_type: "code",
|
|
77
|
+
source_id: null,
|
|
78
|
+
created_at: 0,
|
|
79
|
+
updated_at: 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
var instance = null;
|
|
83
|
+
function getCollectionRegistry() {
|
|
84
|
+
if (!instance) {
|
|
85
|
+
instance = new CollectionRegistry();
|
|
86
|
+
}
|
|
87
|
+
return instance;
|
|
88
|
+
}
|
|
89
|
+
function resetCollectionRegistry() {
|
|
90
|
+
instance = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
3
93
|
// src/services/collection-loader.ts
|
|
94
|
+
function isCodeCollectionInternal(cfg) {
|
|
95
|
+
return cfg.internal === true;
|
|
96
|
+
}
|
|
97
|
+
function isDbDocTypeInternal(source) {
|
|
98
|
+
return source === "system" || source === "plugin";
|
|
99
|
+
}
|
|
100
|
+
async function getVisibleCollections(db) {
|
|
101
|
+
const codeRegistry = getCollectionRegistry().list();
|
|
102
|
+
const codeMap = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const cfg of codeRegistry) {
|
|
104
|
+
if (!isCodeCollectionInternal(cfg)) {
|
|
105
|
+
codeMap.set(cfg.name, { name: cfg.name, displayName: cfg.displayName });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let dbRows = [];
|
|
109
|
+
try {
|
|
110
|
+
const { results } = await db.prepare(
|
|
111
|
+
"SELECT name, display_name, source FROM document_types WHERE is_active = 1 ORDER BY display_name"
|
|
112
|
+
).all();
|
|
113
|
+
dbRows = results ?? [];
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
const merged = new Map(codeMap);
|
|
117
|
+
for (const row of dbRows) {
|
|
118
|
+
if (!isDbDocTypeInternal(row.source) && !merged.has(row.name)) {
|
|
119
|
+
merged.set(row.name, { name: String(row.name), displayName: String(row.display_name) });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return Array.from(merged.values());
|
|
123
|
+
}
|
|
4
124
|
var registeredCollections = [];
|
|
5
125
|
function registerCollections(collections) {
|
|
6
126
|
for (const config of collections) {
|
|
@@ -129,621 +249,295 @@ function validateCollectionConfig(config) {
|
|
|
129
249
|
errors
|
|
130
250
|
};
|
|
131
251
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
results.push(result);
|
|
145
|
-
}
|
|
146
|
-
const created = results.filter((r) => r.status === "created").length;
|
|
147
|
-
const updated = results.filter((r) => r.status === "updated").length;
|
|
148
|
-
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
149
|
-
const errors = results.filter((r) => r.status === "error").length;
|
|
150
|
-
console.log(`\u2705 Collection sync complete: ${created} created, ${updated} updated, ${unchanged} unchanged, ${errors} errors`);
|
|
151
|
-
return results;
|
|
252
|
+
var TENANT_COOKIE = "sonicjs-tenant";
|
|
253
|
+
var TENANT_SWITCHER_MARKER = "<!-- TENANT_SWITCHER -->";
|
|
254
|
+
var MULTI_TENANT_PLUGIN_ID = "multi-tenant";
|
|
255
|
+
var CACHE_TTL_MS = 3e4;
|
|
256
|
+
var DEFAULT_SETTINGS = {
|
|
257
|
+
headerName: "X-Tenant-Id",
|
|
258
|
+
subdomainResolution: false,
|
|
259
|
+
rootDomain: ""
|
|
260
|
+
};
|
|
261
|
+
var cache = null;
|
|
262
|
+
function invalidateTenantCache() {
|
|
263
|
+
cache = null;
|
|
152
264
|
}
|
|
153
|
-
async function
|
|
265
|
+
async function loadTenantState(db) {
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
if (cache && now - cache.fetchedAt < CACHE_TTL_MS) return cache;
|
|
268
|
+
let pluginActive = false;
|
|
269
|
+
let settings = DEFAULT_SETTINGS;
|
|
270
|
+
const tenants = /* @__PURE__ */ new Map();
|
|
271
|
+
const domains = /* @__PURE__ */ new Map();
|
|
154
272
|
try {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const managed = config.managed !== false ? 1 : 0;
|
|
170
|
-
if (!existing) {
|
|
171
|
-
const insertStmt = db.prepare(`
|
|
172
|
-
INSERT INTO collections (id, name, display_name, description, schema, is_active, managed, created_at, updated_at)
|
|
173
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
174
|
-
`);
|
|
175
|
-
await insertStmt.bind(
|
|
176
|
-
collectionId,
|
|
177
|
-
config.name,
|
|
178
|
-
config.displayName,
|
|
179
|
-
config.description || null,
|
|
180
|
-
schemaJson,
|
|
181
|
-
isActive,
|
|
182
|
-
managed,
|
|
183
|
-
now,
|
|
184
|
-
now
|
|
185
|
-
).run();
|
|
186
|
-
console.log(` \u2713 Created collection: ${config.name}`);
|
|
187
|
-
return {
|
|
188
|
-
name: config.name,
|
|
189
|
-
status: "created",
|
|
190
|
-
message: `Created collection "${config.displayName}"`
|
|
191
|
-
};
|
|
192
|
-
} else {
|
|
193
|
-
const existingSchema = existing.schema ? JSON.stringify(existing.schema) : "{}";
|
|
194
|
-
const existingDisplayName = existing.display_name;
|
|
195
|
-
const existingDescription = existing.description;
|
|
196
|
-
const existingIsActive = existing.is_active;
|
|
197
|
-
const existingManaged = existing.managed;
|
|
198
|
-
const needsUpdate = schemaJson !== existingSchema || config.displayName !== existingDisplayName || (config.description || null) !== existingDescription || isActive !== existingIsActive || managed !== existingManaged;
|
|
199
|
-
if (!needsUpdate) {
|
|
200
|
-
return {
|
|
201
|
-
name: config.name,
|
|
202
|
-
status: "unchanged",
|
|
203
|
-
message: `Collection "${config.displayName}" is up to date`
|
|
273
|
+
const pluginRow = await db.prepare(
|
|
274
|
+
`SELECT data FROM documents
|
|
275
|
+
WHERE type_id = 'plugin' AND tenant_id = 'default' AND slug = ?
|
|
276
|
+
AND q_plugin_status = 'active' AND is_current_draft = 1 AND deleted_at IS NULL`
|
|
277
|
+
).bind(MULTI_TENANT_PLUGIN_ID).first();
|
|
278
|
+
if (pluginRow) {
|
|
279
|
+
pluginActive = true;
|
|
280
|
+
try {
|
|
281
|
+
const data = typeof pluginRow.data === "string" ? JSON.parse(pluginRow.data) : pluginRow.data ?? {};
|
|
282
|
+
const s = data.settings ?? {};
|
|
283
|
+
settings = {
|
|
284
|
+
headerName: typeof s.headerName === "string" && s.headerName.trim() !== "" ? s.headerName.trim() : DEFAULT_SETTINGS.headerName,
|
|
285
|
+
subdomainResolution: s.subdomainResolution === true,
|
|
286
|
+
rootDomain: typeof s.rootDomain === "string" ? s.rootDomain.trim().toLowerCase() : ""
|
|
204
287
|
};
|
|
288
|
+
} catch {
|
|
289
|
+
settings = DEFAULT_SETTINGS;
|
|
205
290
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
schemaJson,
|
|
215
|
-
isActive,
|
|
216
|
-
managed,
|
|
217
|
-
now,
|
|
218
|
-
config.name
|
|
219
|
-
).run();
|
|
220
|
-
console.log(` \u2713 Updated collection: ${config.name}`);
|
|
221
|
-
return {
|
|
222
|
-
name: config.name,
|
|
223
|
-
status: "updated",
|
|
224
|
-
message: `Updated collection "${config.displayName}"`
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
} catch (error) {
|
|
228
|
-
console.error(` \u2717 Error syncing collection ${config.name}:`, error);
|
|
229
|
-
return {
|
|
230
|
-
name: config.name,
|
|
231
|
-
status: "error",
|
|
232
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
async function isCollectionManaged(db, collectionName) {
|
|
237
|
-
try {
|
|
238
|
-
const stmt = db.prepare("SELECT managed FROM collections WHERE name = ?");
|
|
239
|
-
const result = await stmt.bind(collectionName).first();
|
|
240
|
-
return result?.managed === 1;
|
|
241
|
-
} catch (error) {
|
|
242
|
-
console.error(`Error checking if collection is managed:`, error);
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
async function getManagedCollections(db) {
|
|
247
|
-
try {
|
|
248
|
-
const stmt = db.prepare("SELECT name FROM collections WHERE managed = 1");
|
|
249
|
-
const { results } = await stmt.all();
|
|
250
|
-
return (results || []).map((row) => row.name);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
console.error("Error getting managed collections:", error);
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
async function cleanupRemovedCollections(db) {
|
|
257
|
-
try {
|
|
258
|
-
const configs = await loadCollectionConfigs();
|
|
259
|
-
const configNames = new Set(configs.map((c) => c.name));
|
|
260
|
-
const managedCollections = await getManagedCollections(db);
|
|
261
|
-
const removed = [];
|
|
262
|
-
for (const managedName of managedCollections) {
|
|
263
|
-
if (!configNames.has(managedName)) {
|
|
264
|
-
const updateStmt = db.prepare(`
|
|
265
|
-
UPDATE collections
|
|
266
|
-
SET is_active = 0, updated_at = ?
|
|
267
|
-
WHERE name = ? AND managed = 1
|
|
268
|
-
`);
|
|
269
|
-
await updateStmt.bind(Date.now(), managedName).run();
|
|
270
|
-
removed.push(managedName);
|
|
271
|
-
console.log(` \u26A0\uFE0F Deactivated removed collection: ${managedName}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return removed;
|
|
275
|
-
} catch (error) {
|
|
276
|
-
console.error("Error cleaning up removed collections:", error);
|
|
277
|
-
return [];
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
async function fullCollectionSync(db) {
|
|
281
|
-
const results = await syncCollections(db);
|
|
282
|
-
const removed = await cleanupRemovedCollections(db);
|
|
283
|
-
return { results, removed };
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// src/services/form-collection-sync.ts
|
|
287
|
-
var SYSTEM_FORM_USER_ID = "system-form-submission";
|
|
288
|
-
function mapFormioTypeToSchemaType(component) {
|
|
289
|
-
switch (component.type) {
|
|
290
|
-
case "textfield":
|
|
291
|
-
case "textarea":
|
|
292
|
-
case "password":
|
|
293
|
-
case "phoneNumber":
|
|
294
|
-
case "url":
|
|
295
|
-
return { type: "string", title: component.label || component.key };
|
|
296
|
-
case "email":
|
|
297
|
-
return { type: "string", format: "email", title: component.label || component.key };
|
|
298
|
-
case "number":
|
|
299
|
-
case "currency":
|
|
300
|
-
return { type: "number", title: component.label || component.key };
|
|
301
|
-
case "checkbox":
|
|
302
|
-
return { type: "boolean", title: component.label || component.key };
|
|
303
|
-
case "select":
|
|
304
|
-
case "radio": {
|
|
305
|
-
const enumValues = (component.data?.values || component.values || []).map((v) => v.value);
|
|
306
|
-
const enumLabels = (component.data?.values || component.values || []).map((v) => v.label);
|
|
307
|
-
return {
|
|
308
|
-
type: "select",
|
|
309
|
-
title: component.label || component.key,
|
|
310
|
-
enum: enumValues,
|
|
311
|
-
enumLabels
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
case "selectboxes":
|
|
315
|
-
return { type: "object", title: component.label || component.key };
|
|
316
|
-
case "datetime":
|
|
317
|
-
case "day":
|
|
318
|
-
case "time":
|
|
319
|
-
return { type: "string", format: "date-time", title: component.label || component.key };
|
|
320
|
-
case "file":
|
|
321
|
-
case "signature":
|
|
322
|
-
return { type: "string", title: component.label || component.key };
|
|
323
|
-
case "address":
|
|
324
|
-
return { type: "object", title: component.label || component.key };
|
|
325
|
-
case "hidden":
|
|
326
|
-
return { type: "string", title: component.label || component.key };
|
|
327
|
-
default:
|
|
328
|
-
return { type: "string", title: component.label || component.key };
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function extractFieldComponents(components) {
|
|
332
|
-
const fields = [];
|
|
333
|
-
if (!components) return fields;
|
|
334
|
-
for (const comp of components) {
|
|
335
|
-
if (comp.type === "panel" || comp.type === "fieldset" || comp.type === "well" || comp.type === "tabs") {
|
|
336
|
-
if (comp.components) {
|
|
337
|
-
fields.push(...extractFieldComponents(comp.components));
|
|
338
|
-
}
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
if (comp.type === "columns" && comp.columns) {
|
|
342
|
-
for (const col of comp.columns) {
|
|
343
|
-
if (col.components) {
|
|
344
|
-
fields.push(...extractFieldComponents(col.components));
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
if (comp.type === "table" && comp.rows) {
|
|
350
|
-
for (const row of comp.rows) {
|
|
351
|
-
if (Array.isArray(row)) {
|
|
352
|
-
for (const cell of row) {
|
|
353
|
-
if (cell.components) {
|
|
354
|
-
fields.push(...extractFieldComponents(cell.components));
|
|
355
|
-
}
|
|
356
|
-
}
|
|
291
|
+
const { results } = await db.prepare(
|
|
292
|
+
`SELECT slug, name, status, domain FROM auth_tenant`
|
|
293
|
+
).all();
|
|
294
|
+
for (const row of results ?? []) {
|
|
295
|
+
const status = row.status === "inactive" ? "inactive" : "active";
|
|
296
|
+
tenants.set(row.slug, { name: row.name || row.slug, status });
|
|
297
|
+
if (row.domain && status === "active") {
|
|
298
|
+
domains.set(String(row.domain).toLowerCase(), row.slug);
|
|
357
299
|
}
|
|
358
300
|
}
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
if (comp.type === "button" || comp.type === "htmlelement" || comp.type === "content") {
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
if (comp.type === "turnstile") {
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
if (comp.key) {
|
|
368
|
-
fields.push(comp);
|
|
369
|
-
}
|
|
370
|
-
if (comp.components) {
|
|
371
|
-
fields.push(...extractFieldComponents(comp.components));
|
|
301
|
+
if (!tenants.has("default")) tenants.set("default", { name: "Default", status: "active" });
|
|
372
302
|
}
|
|
303
|
+
} catch {
|
|
304
|
+
pluginActive = false;
|
|
373
305
|
}
|
|
374
|
-
|
|
306
|
+
cache = { pluginActive, settings, tenants, domains, fetchedAt: now };
|
|
307
|
+
return cache;
|
|
375
308
|
}
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
// Always include a title field for the content item
|
|
381
|
-
title: { type: "string", title: "Title", required: true }
|
|
382
|
-
};
|
|
383
|
-
const required = ["title"];
|
|
384
|
-
for (const comp of fieldComponents) {
|
|
385
|
-
const key = comp.key;
|
|
386
|
-
if (!key || key === "submit" || key === "title") continue;
|
|
387
|
-
const fieldDef = mapFormioTypeToSchemaType(comp);
|
|
388
|
-
if (comp.validate?.required) {
|
|
389
|
-
fieldDef.required = true;
|
|
390
|
-
required.push(key);
|
|
391
|
-
}
|
|
392
|
-
properties[key] = fieldDef;
|
|
393
|
-
}
|
|
394
|
-
return { type: "object", properties, required };
|
|
309
|
+
function isActiveTenant(state, slug) {
|
|
310
|
+
if (!slug) return false;
|
|
311
|
+
const entry = state.tenants.get(slug);
|
|
312
|
+
return !!entry && entry.status === "active";
|
|
395
313
|
}
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
314
|
+
function resolveTenantSlug(state, req, opts) {
|
|
315
|
+
if (!state.pluginActive) return "default";
|
|
316
|
+
const full = state;
|
|
317
|
+
const enforce = opts?.enforceMembership === true;
|
|
318
|
+
const member = (slug) => !enforce || slug === "default" || (opts?.memberSlugs?.has(slug) ?? false);
|
|
319
|
+
const accept = (slug) => isActiveTenant(full, slug) && member(slug);
|
|
320
|
+
const header = req.header?.trim().toLowerCase();
|
|
321
|
+
if (accept(header)) return header;
|
|
322
|
+
const cookie = req.cookie?.trim().toLowerCase();
|
|
323
|
+
if (accept(cookie)) return cookie;
|
|
324
|
+
const host = req.host?.split(":")[0]?.toLowerCase() ?? "";
|
|
325
|
+
if (host) {
|
|
326
|
+
const bySlug = state.domains.get(host);
|
|
327
|
+
if (accept(bySlug)) return bySlug;
|
|
328
|
+
const { subdomainResolution, rootDomain } = state.settings;
|
|
329
|
+
if (subdomainResolution && rootDomain && host.endsWith(`.${rootDomain}`)) {
|
|
330
|
+
const sub = host.slice(0, -(rootDomain.length + 1));
|
|
331
|
+
if (sub && !sub.includes(".") && accept(sub)) return sub;
|
|
405
332
|
}
|
|
406
333
|
}
|
|
407
|
-
|
|
408
|
-
return data.email.trim();
|
|
409
|
-
}
|
|
410
|
-
if (data.subject && typeof data.subject === "string" && data.subject.trim()) {
|
|
411
|
-
return data.subject.trim();
|
|
412
|
-
}
|
|
413
|
-
const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
|
|
414
|
-
year: "numeric",
|
|
415
|
-
month: "short",
|
|
416
|
-
day: "numeric",
|
|
417
|
-
hour: "2-digit",
|
|
418
|
-
minute: "2-digit"
|
|
419
|
-
});
|
|
420
|
-
return `${formDisplayName} - ${dateStr}`;
|
|
421
|
-
}
|
|
422
|
-
function mapFormStatusToContentStatus(formStatus) {
|
|
423
|
-
switch (formStatus) {
|
|
424
|
-
case "pending":
|
|
425
|
-
return "published";
|
|
426
|
-
case "reviewed":
|
|
427
|
-
return "published";
|
|
428
|
-
case "approved":
|
|
429
|
-
return "published";
|
|
430
|
-
case "rejected":
|
|
431
|
-
return "archived";
|
|
432
|
-
case "spam":
|
|
433
|
-
return "deleted";
|
|
434
|
-
default:
|
|
435
|
-
return "published";
|
|
436
|
-
}
|
|
334
|
+
return "default";
|
|
437
335
|
}
|
|
438
|
-
async function
|
|
439
|
-
const collectionName = `form_${form.name}`;
|
|
440
|
-
const displayName = `${form.display_name} (Form)`;
|
|
441
|
-
const formioSchema = typeof form.formio_schema === "string" ? JSON.parse(form.formio_schema) : form.formio_schema;
|
|
442
|
-
const schema = deriveCollectionSchemaFromFormio(formioSchema);
|
|
443
|
-
const schemaJson = JSON.stringify(schema);
|
|
444
|
-
const now = Date.now();
|
|
445
|
-
const isActive = form.is_active ? 1 : 0;
|
|
446
|
-
const existing = await db.prepare(
|
|
447
|
-
"SELECT id, schema, display_name, description, is_active FROM collections WHERE source_type = ? AND source_id = ?"
|
|
448
|
-
).bind("form", form.id).first();
|
|
449
|
-
if (!existing) {
|
|
450
|
-
const collectionId = `col-form-${form.name}-${crypto.randomUUID().slice(0, 8)}`;
|
|
451
|
-
await db.prepare(`
|
|
452
|
-
INSERT INTO collections (id, name, display_name, description, schema, is_active, managed, source_type, source_id, created_at, updated_at)
|
|
453
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, 'form', ?, ?, ?)
|
|
454
|
-
`).bind(
|
|
455
|
-
collectionId,
|
|
456
|
-
collectionName,
|
|
457
|
-
displayName,
|
|
458
|
-
form.description || null,
|
|
459
|
-
schemaJson,
|
|
460
|
-
isActive,
|
|
461
|
-
form.id,
|
|
462
|
-
now,
|
|
463
|
-
now
|
|
464
|
-
).run();
|
|
465
|
-
console.log(`[FormSync] Created shadow collection: ${collectionName}`);
|
|
466
|
-
return { collectionId, status: "created" };
|
|
467
|
-
}
|
|
468
|
-
const existingSchema = existing.schema ? JSON.stringify(typeof existing.schema === "string" ? JSON.parse(existing.schema) : existing.schema) : "{}";
|
|
469
|
-
const needsUpdate = schemaJson !== existingSchema || displayName !== existing.display_name || (form.description || null) !== existing.description || isActive !== existing.is_active;
|
|
470
|
-
if (!needsUpdate) {
|
|
471
|
-
return { collectionId: existing.id, status: "unchanged" };
|
|
472
|
-
}
|
|
473
|
-
await db.prepare(`
|
|
474
|
-
UPDATE collections SET display_name = ?, description = ?, schema = ?, is_active = ?, updated_at = ?
|
|
475
|
-
WHERE id = ?
|
|
476
|
-
`).bind(
|
|
477
|
-
displayName,
|
|
478
|
-
form.description || null,
|
|
479
|
-
schemaJson,
|
|
480
|
-
isActive,
|
|
481
|
-
now,
|
|
482
|
-
existing.id
|
|
483
|
-
).run();
|
|
484
|
-
console.log(`[FormSync] Updated shadow collection: ${collectionName}`);
|
|
485
|
-
return { collectionId: existing.id, status: "updated" };
|
|
486
|
-
}
|
|
487
|
-
async function syncAllFormCollections(db) {
|
|
336
|
+
async function loadMemberRoles(db, userId) {
|
|
488
337
|
try {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
"SELECT id, name, display_name, description, formio_schema, is_active FROM forms"
|
|
498
|
-
).all();
|
|
499
|
-
if (!forms || forms.length === 0) {
|
|
500
|
-
console.log("[FormSync] No forms found, skipping");
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
let created = 0;
|
|
504
|
-
let updated = 0;
|
|
505
|
-
for (const form of forms) {
|
|
506
|
-
try {
|
|
507
|
-
const result = await syncFormCollection(db, form);
|
|
508
|
-
if (result.status === "created") created++;
|
|
509
|
-
if (result.status === "updated") updated++;
|
|
510
|
-
await backfillFormSubmissions(db, form.id, result.collectionId);
|
|
511
|
-
} catch (error) {
|
|
512
|
-
console.error(`[FormSync] Error syncing form ${form.name}:`, error);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
console.log(`[FormSync] Sync complete: ${created} created, ${updated} updated out of ${forms.length} forms`);
|
|
516
|
-
} catch (error) {
|
|
517
|
-
console.error("[FormSync] Error syncing form collections:", error);
|
|
338
|
+
const { results } = await db.prepare(`
|
|
339
|
+
SELECT t.slug, m.role FROM auth_tenant_member m
|
|
340
|
+
JOIN auth_tenant t ON t.id = m.tenant_id
|
|
341
|
+
WHERE m.user_id = ?
|
|
342
|
+
`).bind(userId).all();
|
|
343
|
+
return new Map((results ?? []).map((r) => [r.slug, r.role || "viewer"]));
|
|
344
|
+
} catch {
|
|
345
|
+
return /* @__PURE__ */ new Map();
|
|
518
346
|
}
|
|
519
347
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
console.warn(`[FormSync] No shadow collection found for form ${form.name}, attempting to create...`);
|
|
527
|
-
try {
|
|
528
|
-
const fullForm = await db.prepare(
|
|
529
|
-
"SELECT id, name, display_name, description, formio_schema, is_active FROM forms WHERE id = ?"
|
|
530
|
-
).bind(form.id).first();
|
|
531
|
-
if (fullForm) {
|
|
532
|
-
const schema = typeof fullForm.formio_schema === "string" ? JSON.parse(fullForm.formio_schema) : fullForm.formio_schema;
|
|
533
|
-
const result = await syncFormCollection(db, {
|
|
534
|
-
id: fullForm.id,
|
|
535
|
-
name: fullForm.name,
|
|
536
|
-
display_name: fullForm.display_name,
|
|
537
|
-
description: fullForm.description,
|
|
538
|
-
formio_schema: schema,
|
|
539
|
-
is_active: fullForm.is_active ?? 1
|
|
540
|
-
});
|
|
541
|
-
collection = await db.prepare(
|
|
542
|
-
"SELECT id FROM collections WHERE source_type = ? AND source_id = ?"
|
|
543
|
-
).bind("form", form.id).first();
|
|
544
|
-
console.log(`[FormSync] On-the-fly sync result: ${result.status}, collectionId: ${result.collectionId}`);
|
|
545
|
-
}
|
|
546
|
-
} catch (syncErr) {
|
|
547
|
-
console.error("[FormSync] On-the-fly shadow collection creation failed:", syncErr);
|
|
548
|
-
}
|
|
549
|
-
if (!collection) {
|
|
550
|
-
console.error(`[FormSync] Still no shadow collection for form ${form.name} after recovery attempt`);
|
|
551
|
-
return null;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
const contentId = crypto.randomUUID();
|
|
555
|
-
const now = Date.now();
|
|
556
|
-
const title = deriveSubmissionTitle(submissionData, form.display_name);
|
|
557
|
-
const slug = `submission-${submissionId.slice(0, 8)}`;
|
|
558
|
-
const contentData = {
|
|
559
|
-
title,
|
|
560
|
-
...submissionData,
|
|
561
|
-
_submission_metadata: {
|
|
562
|
-
submissionId,
|
|
563
|
-
formId: form.id,
|
|
564
|
-
formName: form.name,
|
|
565
|
-
email: metadata.userEmail || submissionData.email || null,
|
|
566
|
-
ipAddress: metadata.ipAddress || null,
|
|
567
|
-
userAgent: metadata.userAgent || null,
|
|
568
|
-
submittedAt: now
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
const authorId = metadata.userId || SYSTEM_FORM_USER_ID;
|
|
572
|
-
if (authorId === SYSTEM_FORM_USER_ID) {
|
|
573
|
-
const systemUser = await db.prepare("SELECT id FROM users WHERE id = ?").bind(SYSTEM_FORM_USER_ID).first();
|
|
574
|
-
if (!systemUser) {
|
|
575
|
-
console.log("[FormSync] System form user missing, creating...");
|
|
576
|
-
const sysNow = Date.now();
|
|
577
|
-
await db.prepare(`
|
|
578
|
-
INSERT OR IGNORE INTO users (id, email, username, first_name, last_name, password_hash, role, is_active, created_at, updated_at)
|
|
579
|
-
VALUES (?, ?, ?, ?, ?, NULL, 'viewer', 0, ?, ?)
|
|
580
|
-
`).bind(SYSTEM_FORM_USER_ID, "system-forms@sonicjs.internal", "system-forms", "Form", "Submission", sysNow, sysNow).run();
|
|
581
|
-
}
|
|
348
|
+
function tenantMiddleware() {
|
|
349
|
+
return async (c, next) => {
|
|
350
|
+
const db = c.env?.DB;
|
|
351
|
+
if (!db) {
|
|
352
|
+
c.set("tenantId", "default");
|
|
353
|
+
return next();
|
|
582
354
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
title,
|
|
592
|
-
JSON.stringify(contentData),
|
|
593
|
-
authorId,
|
|
594
|
-
now,
|
|
595
|
-
now
|
|
596
|
-
).run();
|
|
597
|
-
await db.prepare(
|
|
598
|
-
"UPDATE form_submissions SET content_id = ? WHERE id = ?"
|
|
599
|
-
).bind(contentId, submissionId).run();
|
|
600
|
-
console.log(`[FormSync] Content created successfully: ${contentId}`);
|
|
601
|
-
return contentId;
|
|
602
|
-
} catch (error) {
|
|
603
|
-
console.error("[FormSync] Error creating content from submission:", error);
|
|
604
|
-
return null;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
async function backfillFormSubmissions(db, formId, collectionId) {
|
|
608
|
-
try {
|
|
609
|
-
const { results: submissions } = await db.prepare(
|
|
610
|
-
"SELECT id, submission_data, user_email, ip_address, user_agent, user_id, submitted_at FROM form_submissions WHERE form_id = ? AND content_id IS NULL"
|
|
611
|
-
).bind(formId).all();
|
|
612
|
-
if (!submissions || submissions.length === 0) {
|
|
613
|
-
return 0;
|
|
355
|
+
const state = await loadTenantState(db);
|
|
356
|
+
const user = c.get("user");
|
|
357
|
+
let memberSlugs;
|
|
358
|
+
let memberRoles;
|
|
359
|
+
const enforceMembership = !!(user?.userId && state.pluginActive && !user.isSuperAdmin);
|
|
360
|
+
if (user?.userId && state.pluginActive && !user.isSuperAdmin) {
|
|
361
|
+
memberRoles = await loadMemberRoles(db, user.userId);
|
|
362
|
+
memberSlugs = new Set(memberRoles.keys());
|
|
614
363
|
}
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
{
|
|
629
|
-
ipAddress: sub.ip_address,
|
|
630
|
-
userAgent: sub.user_agent,
|
|
631
|
-
userEmail: sub.user_email,
|
|
632
|
-
userId: sub.user_id
|
|
633
|
-
}
|
|
634
|
-
);
|
|
635
|
-
if (contentId) count++;
|
|
636
|
-
} catch (error) {
|
|
637
|
-
console.error(`[FormSync] Error backfilling submission ${sub.id}:`, error);
|
|
638
|
-
}
|
|
364
|
+
const tenantId = resolveTenantSlug(
|
|
365
|
+
state,
|
|
366
|
+
{
|
|
367
|
+
header: c.req.header(state.settings.headerName),
|
|
368
|
+
cookie: cookie.getCookie(c, TENANT_COOKIE),
|
|
369
|
+
host: c.req.header("host")
|
|
370
|
+
},
|
|
371
|
+
{ memberSlugs, enforceMembership }
|
|
372
|
+
);
|
|
373
|
+
c.set("tenantId", tenantId);
|
|
374
|
+
if (user?.userId) {
|
|
375
|
+
const role = tenantId === "default" || user.isSuperAdmin ? user.role : memberRoles?.get(tenantId) ?? user.role;
|
|
376
|
+
c.set("tenantRole", role);
|
|
639
377
|
}
|
|
640
|
-
|
|
641
|
-
|
|
378
|
+
await next();
|
|
379
|
+
const path = new URL(c.req.url).pathname;
|
|
380
|
+
if (!path.startsWith("/admin")) return;
|
|
381
|
+
if (!c.res.headers.get("content-type")?.includes("text/html")) return;
|
|
382
|
+
const status = c.res.status;
|
|
383
|
+
const headers = new Headers(c.res.headers);
|
|
384
|
+
const html = await c.res.text();
|
|
385
|
+
if (html.includes(TENANT_SWITCHER_MARKER)) {
|
|
386
|
+
const replacement = state.pluginActive ? renderTenantSwitcher(state, tenantId, enforceMembership ? memberSlugs : void 0) : "";
|
|
387
|
+
c.res = new Response(html.split(TENANT_SWITCHER_MARKER).join(replacement), { status, headers });
|
|
388
|
+
} else {
|
|
389
|
+
c.res = new Response(html, { status, headers });
|
|
642
390
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function renderTenantSwitcher(state, currentTenantId, memberSlugs) {
|
|
394
|
+
const active = [...state.tenants.entries()].filter(([slug, t]) => t.status === "active" && (!memberSlugs || slug === "default" || memberSlugs.has(slug))).sort(([a], [b]) => a === "default" ? -1 : b === "default" ? 1 : a.localeCompare(b));
|
|
395
|
+
const options = active.map(
|
|
396
|
+
([slug, t]) => `<option value="${chunkMNWKYY5E_cjs.escapeHtml(slug)}" ${slug === currentTenantId ? "selected" : ""}>${chunkMNWKYY5E_cjs.escapeHtml(t.name)}</option>`
|
|
397
|
+
).join("");
|
|
398
|
+
return `
|
|
399
|
+
<div class="border-b border-zinc-950/5 px-4 py-3 dark:border-white/5" data-tenant-switcher>
|
|
400
|
+
<label for="tenant-switcher-select" class="mb-1 flex items-center gap-1.5 text-xs/5 font-medium text-zinc-500 dark:text-zinc-400">
|
|
401
|
+
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m3-6H15m-1.5 3H15m-1.5 3H15M9 21v-3.375c0-.621.504-1.125 1.125-1.125h3.75c.621 0 1.125.504 1.125 1.125V21"/></svg>
|
|
402
|
+
Tenant
|
|
403
|
+
</label>
|
|
404
|
+
<form method="POST" action="/admin/tenants/switch" data-tenant-switcher-form>
|
|
405
|
+
<select
|
|
406
|
+
id="tenant-switcher-select"
|
|
407
|
+
name="tenant"
|
|
408
|
+
onchange="this.form.requestSubmit ? this.form.requestSubmit() : this.form.submit()"
|
|
409
|
+
class="w-full rounded-lg border border-zinc-950/10 bg-white px-2 py-1.5 text-sm/5 text-zinc-950 dark:border-white/10 dark:bg-zinc-800 dark:text-white"
|
|
410
|
+
>${options}</select>
|
|
411
|
+
<input type="hidden" name="redirect" value="" data-tenant-switcher-redirect>
|
|
412
|
+
</form>
|
|
413
|
+
<script>
|
|
414
|
+
(function () {
|
|
415
|
+
var r = document.querySelector('[data-tenant-switcher-redirect]');
|
|
416
|
+
if (r) r.value = window.location.pathname + window.location.search;
|
|
417
|
+
})();
|
|
418
|
+
</script>
|
|
419
|
+
</div>`;
|
|
648
420
|
}
|
|
649
421
|
|
|
650
422
|
// src/services/plugin-service.ts
|
|
423
|
+
var TENANT = "default";
|
|
424
|
+
var TYPE_ID = "plugin";
|
|
651
425
|
var PluginService = class {
|
|
652
426
|
constructor(db) {
|
|
653
427
|
this.db = db;
|
|
654
428
|
}
|
|
655
429
|
async getAllPlugins() {
|
|
656
|
-
await this.
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
ORDER BY
|
|
660
|
-
`);
|
|
661
|
-
|
|
662
|
-
return (results || []).map(this.mapPluginFromDb);
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Ensure all plugins from the registry exist in the database
|
|
666
|
-
* Auto-installs any newly detected plugins with inactive status
|
|
667
|
-
*
|
|
668
|
-
* Note: This method should be overridden or configured with a plugin registry
|
|
669
|
-
* in the consuming application
|
|
670
|
-
*/
|
|
671
|
-
async ensureAllPluginsExist() {
|
|
672
|
-
console.log("[PluginService] ensureAllPluginsExist - requires PLUGIN_REGISTRY configuration");
|
|
430
|
+
const { results } = await this.db.prepare(`
|
|
431
|
+
SELECT * FROM documents
|
|
432
|
+
WHERE type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
433
|
+
ORDER BY json_extract(data, '$.isCore') DESC, title ASC
|
|
434
|
+
`).bind(TYPE_ID, TENANT).all();
|
|
435
|
+
return (results || []).map(mapDocumentToPlugin);
|
|
673
436
|
}
|
|
674
437
|
async getPlugin(pluginId) {
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
438
|
+
const row = await this.db.prepare(`
|
|
439
|
+
SELECT * FROM documents
|
|
440
|
+
WHERE slug = ? AND type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
441
|
+
`).bind(pluginId, TYPE_ID, TENANT).first();
|
|
442
|
+
if (!row) return null;
|
|
443
|
+
return mapDocumentToPlugin(row);
|
|
679
444
|
}
|
|
680
445
|
async getPluginByName(name) {
|
|
681
|
-
|
|
682
|
-
const plugin = await stmt.bind(name).first();
|
|
683
|
-
if (!plugin) return null;
|
|
684
|
-
return this.mapPluginFromDb(plugin);
|
|
446
|
+
return this.getPlugin(name);
|
|
685
447
|
}
|
|
686
448
|
async getPluginStats() {
|
|
687
|
-
const
|
|
688
|
-
SELECT
|
|
449
|
+
const stats = await this.db.prepare(`
|
|
450
|
+
SELECT
|
|
689
451
|
COUNT(*) as total,
|
|
690
|
-
COUNT(CASE WHEN
|
|
691
|
-
COUNT(CASE WHEN
|
|
692
|
-
COUNT(CASE WHEN
|
|
693
|
-
FROM
|
|
694
|
-
|
|
695
|
-
|
|
452
|
+
COUNT(CASE WHEN q_plugin_status = 'active' THEN 1 END) as active,
|
|
453
|
+
COUNT(CASE WHEN q_plugin_status = 'inactive' THEN 1 END) as inactive,
|
|
454
|
+
COUNT(CASE WHEN q_plugin_status = 'error' THEN 1 END) as errors
|
|
455
|
+
FROM documents
|
|
456
|
+
WHERE type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
457
|
+
`).bind(TYPE_ID, TENANT).first();
|
|
696
458
|
return {
|
|
697
|
-
total: stats
|
|
698
|
-
active: stats
|
|
699
|
-
inactive: stats
|
|
700
|
-
errors: stats
|
|
459
|
+
total: stats?.total || 0,
|
|
460
|
+
active: stats?.active || 0,
|
|
461
|
+
inactive: stats?.inactive || 0,
|
|
462
|
+
errors: stats?.errors || 0,
|
|
701
463
|
uninstalled: 0
|
|
702
464
|
};
|
|
703
465
|
}
|
|
704
466
|
async installPlugin(pluginData) {
|
|
705
|
-
const
|
|
467
|
+
const slug = pluginData.id || pluginData.name || `plugin-${Date.now()}`;
|
|
468
|
+
const docId = crypto.randomUUID();
|
|
706
469
|
const now = Math.floor(Date.now() / 1e3);
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
pluginData.
|
|
470
|
+
const data = JSON.stringify({
|
|
471
|
+
name: slug,
|
|
472
|
+
displayName: pluginData.display_name || "Unnamed Plugin",
|
|
473
|
+
description: pluginData.description || "",
|
|
474
|
+
version: pluginData.version || "1.0.0",
|
|
475
|
+
author: pluginData.author || "Unknown",
|
|
476
|
+
category: pluginData.category || "utilities",
|
|
477
|
+
icon: pluginData.icon || "\u{1F50C}",
|
|
478
|
+
status: "active",
|
|
479
|
+
isCore: pluginData.is_core || false,
|
|
480
|
+
settings: pluginData.settings || {},
|
|
481
|
+
permissions: pluginData.permissions || [],
|
|
482
|
+
dependencies: pluginData.dependencies || [],
|
|
483
|
+
downloadCount: pluginData.download_count || 0,
|
|
484
|
+
rating: pluginData.rating || 0
|
|
485
|
+
});
|
|
486
|
+
await this.db.prepare(`
|
|
487
|
+
INSERT INTO documents (
|
|
488
|
+
id, root_id, type_id, version_number, is_current_draft, is_published, status,
|
|
489
|
+
parent_root_id, slug, title, tenant_id, locale, translation_group_id,
|
|
490
|
+
data, metadata, created_at, updated_at
|
|
491
|
+
) VALUES (
|
|
492
|
+
?, ?, ?, 1, 1, 1, 'published',
|
|
493
|
+
'', ?, ?, ?, 'default', '',
|
|
494
|
+
?, '{}', ?, ?
|
|
495
|
+
)
|
|
496
|
+
`).bind(
|
|
497
|
+
docId,
|
|
498
|
+
docId,
|
|
499
|
+
TYPE_ID,
|
|
500
|
+
slug,
|
|
717
501
|
pluginData.display_name || "Unnamed Plugin",
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
pluginData.author || "Unknown",
|
|
721
|
-
pluginData.category || "utilities",
|
|
722
|
-
pluginData.icon || "\u{1F50C}",
|
|
723
|
-
"inactive",
|
|
724
|
-
pluginData.is_core || false,
|
|
725
|
-
JSON.stringify(pluginData.settings || {}),
|
|
726
|
-
JSON.stringify(pluginData.permissions || []),
|
|
727
|
-
JSON.stringify(pluginData.dependencies || []),
|
|
728
|
-
pluginData.download_count || 0,
|
|
729
|
-
pluginData.rating || 0,
|
|
502
|
+
TENANT,
|
|
503
|
+
data,
|
|
730
504
|
now,
|
|
731
505
|
now
|
|
732
506
|
).run();
|
|
733
|
-
await this.logActivity(
|
|
734
|
-
|
|
507
|
+
await this.logActivity(slug, "installed", null, { version: pluginData.version });
|
|
508
|
+
await this.logActivity(slug, "activated", null);
|
|
509
|
+
const installed = await this.getPlugin(slug);
|
|
735
510
|
if (!installed) throw new Error("Failed to install plugin");
|
|
736
511
|
return installed;
|
|
737
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Ensure a definePlugin-registered plugin exists in the DB with active status.
|
|
515
|
+
* No-op if already present. Used by admin routes to auto-register SDK plugins
|
|
516
|
+
* that have never been explicitly installed.
|
|
517
|
+
*/
|
|
518
|
+
async ensurePlugin(id, data) {
|
|
519
|
+
const existing = await this.getPlugin(id);
|
|
520
|
+
if (existing) return existing;
|
|
521
|
+
return this.installPlugin({
|
|
522
|
+
id,
|
|
523
|
+
name: id,
|
|
524
|
+
display_name: data.displayName || id,
|
|
525
|
+
description: data.description || "",
|
|
526
|
+
author: data.author || "",
|
|
527
|
+
version: data.version || "1.0.0"
|
|
528
|
+
});
|
|
529
|
+
}
|
|
738
530
|
async uninstallPlugin(pluginId) {
|
|
739
531
|
const plugin = await this.getPlugin(pluginId);
|
|
740
532
|
if (!plugin) throw new Error("Plugin not found");
|
|
741
533
|
if (plugin.is_core) throw new Error("Cannot uninstall core plugins");
|
|
742
|
-
if (plugin.status === "active")
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
534
|
+
if (plugin.status === "active") await this.deactivatePlugin(pluginId);
|
|
535
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
536
|
+
await this.db.prepare(`
|
|
537
|
+
UPDATE documents
|
|
538
|
+
SET deleted_at = ?, updated_at = ?, is_current_draft = 0, is_published = 0
|
|
539
|
+
WHERE slug = ? AND type_id = ? AND tenant_id = ? AND is_current_draft = 1
|
|
540
|
+
`).bind(now, now, pluginId, TYPE_ID, TENANT).run();
|
|
747
541
|
await this.logActivity(pluginId, "uninstalled", null, { name: plugin.name });
|
|
748
542
|
}
|
|
749
543
|
async activatePlugin(pluginId) {
|
|
@@ -753,100 +547,128 @@ var PluginService = class {
|
|
|
753
547
|
await this.checkDependencies(plugin.dependencies);
|
|
754
548
|
}
|
|
755
549
|
const now = Math.floor(Date.now() / 1e3);
|
|
756
|
-
|
|
757
|
-
UPDATE
|
|
758
|
-
SET
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
550
|
+
await this.db.prepare(`
|
|
551
|
+
UPDATE documents
|
|
552
|
+
SET data = json_set(data, '$.status', 'active', '$.activatedAt', ?, '$.errorMessage', null),
|
|
553
|
+
updated_at = ?
|
|
554
|
+
WHERE slug = ? AND type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
555
|
+
`).bind(now, now, pluginId, TYPE_ID, TENANT).run();
|
|
556
|
+
await this.db.prepare(`UPDATE plugins SET status = 'active' WHERE id = ?`).bind(pluginId).run().catch(() => {
|
|
557
|
+
});
|
|
558
|
+
invalidateTenantCache();
|
|
762
559
|
await this.logActivity(pluginId, "activated", null);
|
|
763
560
|
}
|
|
764
561
|
async deactivatePlugin(pluginId) {
|
|
765
562
|
const plugin = await this.getPlugin(pluginId);
|
|
766
563
|
if (!plugin) throw new Error("Plugin not found");
|
|
767
564
|
await this.checkDependents(plugin.name);
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
565
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
566
|
+
await this.db.prepare(`
|
|
567
|
+
UPDATE documents
|
|
568
|
+
SET data = json_set(data, '$.status', 'inactive', '$.activatedAt', null),
|
|
569
|
+
updated_at = ?
|
|
570
|
+
WHERE slug = ? AND type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
571
|
+
`).bind(now, pluginId, TYPE_ID, TENANT).run();
|
|
572
|
+
await this.db.prepare(`UPDATE plugins SET status = 'inactive' WHERE id = ?`).bind(pluginId).run().catch(() => {
|
|
573
|
+
});
|
|
574
|
+
invalidateTenantCache();
|
|
774
575
|
await this.logActivity(pluginId, "deactivated", null);
|
|
775
576
|
}
|
|
776
577
|
async updatePluginSettings(pluginId, settings) {
|
|
777
578
|
const plugin = await this.getPlugin(pluginId);
|
|
778
579
|
if (!plugin) throw new Error("Plugin not found");
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
580
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
581
|
+
await this.db.prepare(`
|
|
582
|
+
UPDATE documents
|
|
583
|
+
SET data = json_set(data, '$.settings', json(?)), updated_at = ?
|
|
584
|
+
WHERE slug = ? AND type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
585
|
+
`).bind(JSON.stringify(settings), now, pluginId, TYPE_ID, TENANT).run();
|
|
586
|
+
invalidateTenantCache();
|
|
785
587
|
await this.logActivity(pluginId, "settings_updated", null);
|
|
786
588
|
}
|
|
589
|
+
async updatePluginVersion(pluginId, patch) {
|
|
590
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
591
|
+
await this.db.prepare(`
|
|
592
|
+
UPDATE documents
|
|
593
|
+
SET data = json_set(data,
|
|
594
|
+
'$.version', ?,
|
|
595
|
+
'$.description', ?,
|
|
596
|
+
'$.permissions', json(?),
|
|
597
|
+
'$.settings', json(?)
|
|
598
|
+
),
|
|
599
|
+
updated_at = ?
|
|
600
|
+
WHERE slug = ? AND type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
601
|
+
`).bind(
|
|
602
|
+
patch.version,
|
|
603
|
+
patch.description,
|
|
604
|
+
JSON.stringify(patch.permissions),
|
|
605
|
+
JSON.stringify(patch.settings || {}),
|
|
606
|
+
now,
|
|
607
|
+
pluginId,
|
|
608
|
+
TYPE_ID,
|
|
609
|
+
TENANT
|
|
610
|
+
).run();
|
|
611
|
+
}
|
|
787
612
|
async setPluginError(pluginId, error) {
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
613
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
614
|
+
await this.db.prepare(`
|
|
615
|
+
UPDATE documents
|
|
616
|
+
SET data = json_set(data, '$.status', 'error', '$.errorMessage', ?),
|
|
617
|
+
updated_at = ?
|
|
618
|
+
WHERE slug = ? AND type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
619
|
+
`).bind(error, now, pluginId, TYPE_ID, TENANT).run();
|
|
794
620
|
await this.logActivity(pluginId, "error", null, { error });
|
|
795
621
|
}
|
|
796
622
|
async getPluginActivity(pluginId, limit = 10) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
623
|
+
try {
|
|
624
|
+
const { results } = await this.db.prepare(`
|
|
625
|
+
SELECT id, data, created_at FROM documents
|
|
626
|
+
WHERE type_id = 'plugin_activity'
|
|
627
|
+
AND tenant_id = ?
|
|
628
|
+
AND is_current_draft = 1
|
|
629
|
+
AND deleted_at IS NULL
|
|
630
|
+
AND json_extract(data, '$.pluginId') = ?
|
|
631
|
+
ORDER BY created_at DESC
|
|
632
|
+
LIMIT ?
|
|
633
|
+
`).bind(TENANT, pluginId, limit).all();
|
|
634
|
+
return (results || []).map((row) => {
|
|
635
|
+
const d = typeof row.data === "string" ? JSON.parse(row.data) : row.data || {};
|
|
636
|
+
return {
|
|
637
|
+
id: row.id,
|
|
638
|
+
action: d.action,
|
|
639
|
+
userId: d.userId || null,
|
|
640
|
+
details: d.details || null,
|
|
641
|
+
timestamp: row.created_at
|
|
642
|
+
};
|
|
643
|
+
});
|
|
644
|
+
} catch {
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
811
647
|
}
|
|
812
648
|
async registerHook(pluginId, hookName, handlerName, priority = 10) {
|
|
813
649
|
const id = `hook-${Date.now()}`;
|
|
814
|
-
|
|
650
|
+
await this.db.prepare(`
|
|
815
651
|
INSERT INTO plugin_hooks (id, plugin_id, hook_name, handler_name, priority)
|
|
816
652
|
VALUES (?, ?, ?, ?, ?)
|
|
817
|
-
`);
|
|
818
|
-
await stmt.bind(id, pluginId, hookName, handlerName, priority).run();
|
|
653
|
+
`).bind(id, pluginId, hookName, handlerName, priority).run();
|
|
819
654
|
}
|
|
820
655
|
async registerRoute(pluginId, path, method, handlerName, middleware) {
|
|
821
656
|
const id = `route-${Date.now()}`;
|
|
822
|
-
|
|
657
|
+
await this.db.prepare(`
|
|
823
658
|
INSERT INTO plugin_routes (id, plugin_id, path, method, handler_name, middleware)
|
|
824
659
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
825
|
-
`);
|
|
826
|
-
await stmt.bind(
|
|
827
|
-
id,
|
|
828
|
-
pluginId,
|
|
829
|
-
path,
|
|
830
|
-
method,
|
|
831
|
-
handlerName,
|
|
832
|
-
JSON.stringify(middleware || [])
|
|
833
|
-
).run();
|
|
660
|
+
`).bind(id, pluginId, path, method, handlerName, JSON.stringify(middleware || [])).run();
|
|
834
661
|
}
|
|
835
662
|
async getPluginHooks(pluginId) {
|
|
836
|
-
const
|
|
837
|
-
SELECT * FROM plugin_hooks
|
|
838
|
-
|
|
839
|
-
ORDER BY priority ASC
|
|
840
|
-
`);
|
|
841
|
-
const { results } = await stmt.bind(pluginId).all();
|
|
663
|
+
const { results } = await this.db.prepare(`
|
|
664
|
+
SELECT * FROM plugin_hooks WHERE plugin_id = ? AND is_active = TRUE ORDER BY priority ASC
|
|
665
|
+
`).bind(pluginId).all();
|
|
842
666
|
return results || [];
|
|
843
667
|
}
|
|
844
668
|
async getPluginRoutes(pluginId) {
|
|
845
|
-
const
|
|
846
|
-
SELECT * FROM plugin_routes
|
|
847
|
-
|
|
848
|
-
`);
|
|
849
|
-
const { results } = await stmt.bind(pluginId).all();
|
|
669
|
+
const { results } = await this.db.prepare(`
|
|
670
|
+
SELECT * FROM plugin_routes WHERE plugin_id = ? AND is_active = TRUE
|
|
671
|
+
`).bind(pluginId).all();
|
|
850
672
|
return results || [];
|
|
851
673
|
}
|
|
852
674
|
async checkDependencies(dependencies) {
|
|
@@ -858,55 +680,61 @@ var PluginService = class {
|
|
|
858
680
|
}
|
|
859
681
|
}
|
|
860
682
|
async checkDependents(pluginName) {
|
|
861
|
-
const
|
|
862
|
-
SELECT
|
|
863
|
-
WHERE
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
683
|
+
const { results } = await this.db.prepare(`
|
|
684
|
+
SELECT slug, title FROM documents
|
|
685
|
+
WHERE type_id = ? AND tenant_id = ? AND is_current_draft = 1 AND deleted_at IS NULL
|
|
686
|
+
AND q_plugin_status = 'active'
|
|
687
|
+
AND json_extract(data, '$.dependencies') LIKE ?
|
|
688
|
+
`).bind(TYPE_ID, TENANT, `%"${pluginName}"%`).all();
|
|
867
689
|
if (results && results.length > 0) {
|
|
868
|
-
const names = results.map((p) => p.
|
|
690
|
+
const names = results.map((p) => p.title || p.slug).join(", ");
|
|
869
691
|
throw new Error(`Cannot deactivate. The following plugins depend on this one: ${names}`);
|
|
870
692
|
}
|
|
871
693
|
}
|
|
872
694
|
async logActivity(pluginId, action, userId, details) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
display_name: row.display_name,
|
|
891
|
-
description: row.description,
|
|
892
|
-
version: row.version,
|
|
893
|
-
author: row.author,
|
|
894
|
-
category: row.category,
|
|
895
|
-
icon: row.icon,
|
|
896
|
-
status: row.status,
|
|
897
|
-
is_core: row.is_core === 1,
|
|
898
|
-
settings: row.settings ? JSON.parse(row.settings) : void 0,
|
|
899
|
-
permissions: row.permissions ? JSON.parse(row.permissions) : void 0,
|
|
900
|
-
dependencies: row.dependencies ? JSON.parse(row.dependencies) : void 0,
|
|
901
|
-
download_count: row.download_count || 0,
|
|
902
|
-
rating: row.rating || 0,
|
|
903
|
-
installed_at: row.installed_at,
|
|
904
|
-
activated_at: row.activated_at,
|
|
905
|
-
last_updated: row.last_updated,
|
|
906
|
-
error_message: row.error_message
|
|
907
|
-
};
|
|
695
|
+
try {
|
|
696
|
+
const docId = crypto.randomUUID();
|
|
697
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
698
|
+
const data = JSON.stringify({ pluginId, action, userId, details: details || null });
|
|
699
|
+
await this.db.prepare(`
|
|
700
|
+
INSERT INTO documents (
|
|
701
|
+
id, root_id, type_id, version_number, is_current_draft, is_published, status,
|
|
702
|
+
parent_root_id, slug, title, tenant_id, locale, translation_group_id,
|
|
703
|
+
data, metadata, created_at, updated_at
|
|
704
|
+
) VALUES (
|
|
705
|
+
?, ?, 'plugin_activity', 1, 1, 1, 'published',
|
|
706
|
+
'', ?, ?, 'default', 'default', '',
|
|
707
|
+
?, '{}', ?, ?
|
|
708
|
+
)
|
|
709
|
+
`).bind(docId, docId, docId, action, data, now, now).run();
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
908
712
|
}
|
|
909
713
|
};
|
|
714
|
+
function mapDocumentToPlugin(row) {
|
|
715
|
+
const data = typeof row.data === "string" ? JSON.parse(row.data) : row.data || {};
|
|
716
|
+
return {
|
|
717
|
+
id: row.slug || data.name || row.root_id,
|
|
718
|
+
name: data.name || row.slug || "",
|
|
719
|
+
display_name: data.displayName || row.title || "",
|
|
720
|
+
description: data.description || "",
|
|
721
|
+
version: data.version || "1.0.0",
|
|
722
|
+
author: data.author || "Unknown",
|
|
723
|
+
category: data.category || "utilities",
|
|
724
|
+
icon: data.icon || "\u{1F50C}",
|
|
725
|
+
status: data.status || "inactive",
|
|
726
|
+
is_core: data.isCore === true || data.isCore === 1,
|
|
727
|
+
settings: data.settings,
|
|
728
|
+
permissions: data.permissions,
|
|
729
|
+
dependencies: data.dependencies,
|
|
730
|
+
download_count: data.downloadCount || 0,
|
|
731
|
+
rating: data.rating || 0,
|
|
732
|
+
installed_at: row.created_at,
|
|
733
|
+
activated_at: data.activatedAt || void 0,
|
|
734
|
+
last_updated: row.updated_at,
|
|
735
|
+
error_message: data.errorMessage || void 0
|
|
736
|
+
};
|
|
737
|
+
}
|
|
910
738
|
|
|
911
739
|
// src/plugins/manifest-registry.ts
|
|
912
740
|
var PLUGIN_REGISTRY = {
|
|
@@ -943,23 +771,6 @@ var PLUGIN_REGISTRY = {
|
|
|
943
771
|
"order": 50
|
|
944
772
|
}
|
|
945
773
|
},
|
|
946
|
-
"code-examples-plugin": {
|
|
947
|
-
"id": "code-examples-plugin",
|
|
948
|
-
"codeName": "code-examples-plugin",
|
|
949
|
-
"displayName": "Code Examples",
|
|
950
|
-
"description": "Code snippets and examples library with syntax highlighting and categorization",
|
|
951
|
-
"version": "1.0.0-beta.1",
|
|
952
|
-
"author": "SonicJS Team",
|
|
953
|
-
"category": "content",
|
|
954
|
-
"iconEmoji": "\u{1F4BB}",
|
|
955
|
-
"is_core": false,
|
|
956
|
-
"permissions": [
|
|
957
|
-
"code-examples:manage"
|
|
958
|
-
],
|
|
959
|
-
"dependencies": [],
|
|
960
|
-
"defaultSettings": {},
|
|
961
|
-
"adminMenu": null
|
|
962
|
-
},
|
|
963
774
|
"core-analytics": {
|
|
964
775
|
"id": "core-analytics",
|
|
965
776
|
"codeName": "core-analytics",
|
|
@@ -1013,12 +824,6 @@ var PLUGIN_REGISTRY = {
|
|
|
1013
824
|
"label": "Password",
|
|
1014
825
|
"type": "password"
|
|
1015
826
|
},
|
|
1016
|
-
"username": {
|
|
1017
|
-
"required": true,
|
|
1018
|
-
"minLength": 3,
|
|
1019
|
-
"label": "Username",
|
|
1020
|
-
"type": "text"
|
|
1021
|
-
},
|
|
1022
827
|
"firstName": {
|
|
1023
828
|
"required": true,
|
|
1024
829
|
"minLength": 1,
|
|
@@ -1034,7 +839,6 @@ var PLUGIN_REGISTRY = {
|
|
|
1034
839
|
},
|
|
1035
840
|
"validation": {
|
|
1036
841
|
"emailFormat": true,
|
|
1037
|
-
"allowDuplicateUsernames": false,
|
|
1038
842
|
"passwordRequirements": {
|
|
1039
843
|
"requireUppercase": false,
|
|
1040
844
|
"requireLowercase": false,
|
|
@@ -1072,7 +876,12 @@ var PLUGIN_REGISTRY = {
|
|
|
1072
876
|
"enableDatabaseCache": true,
|
|
1073
877
|
"defaultTTL": 3600
|
|
1074
878
|
},
|
|
1075
|
-
"adminMenu":
|
|
879
|
+
"adminMenu": {
|
|
880
|
+
"label": "Cache",
|
|
881
|
+
"icon": "server",
|
|
882
|
+
"path": "/admin/cache",
|
|
883
|
+
"order": 60
|
|
884
|
+
}
|
|
1076
885
|
},
|
|
1077
886
|
"core-media": {
|
|
1078
887
|
"id": "core-media",
|
|
@@ -1106,7 +915,7 @@ var PLUGIN_REGISTRY = {
|
|
|
1106
915
|
"author": "SonicJS Team",
|
|
1107
916
|
"category": "development",
|
|
1108
917
|
"iconEmoji": "\u{1F5C4}\uFE0F",
|
|
1109
|
-
"is_core":
|
|
918
|
+
"is_core": true,
|
|
1110
919
|
"permissions": [
|
|
1111
920
|
"database:admin"
|
|
1112
921
|
],
|
|
@@ -1140,32 +949,6 @@ var PLUGIN_REGISTRY = {
|
|
|
1140
949
|
},
|
|
1141
950
|
"adminMenu": null
|
|
1142
951
|
},
|
|
1143
|
-
"design": {
|
|
1144
|
-
"id": "design",
|
|
1145
|
-
"codeName": "design",
|
|
1146
|
-
"displayName": "Design System",
|
|
1147
|
-
"description": "Design system management including themes, components, and UI customization. Provides a visual interface for managing design tokens, typography, colors, and component library.",
|
|
1148
|
-
"version": "1.0.0-beta.1",
|
|
1149
|
-
"author": "SonicJS",
|
|
1150
|
-
"category": "utilities",
|
|
1151
|
-
"iconEmoji": "\u{1F3A8}",
|
|
1152
|
-
"is_core": false,
|
|
1153
|
-
"permissions": [
|
|
1154
|
-
"design.view",
|
|
1155
|
-
"design.edit"
|
|
1156
|
-
],
|
|
1157
|
-
"dependencies": [],
|
|
1158
|
-
"defaultSettings": {
|
|
1159
|
-
"defaultTheme": "light",
|
|
1160
|
-
"customCSS": ""
|
|
1161
|
-
},
|
|
1162
|
-
"adminMenu": {
|
|
1163
|
-
"label": "Design",
|
|
1164
|
-
"icon": "palette",
|
|
1165
|
-
"path": "/admin/design",
|
|
1166
|
-
"order": 80
|
|
1167
|
-
}
|
|
1168
|
-
},
|
|
1169
952
|
"easy-mdx": {
|
|
1170
953
|
"id": "easy-mdx",
|
|
1171
954
|
"codeName": "easy-mdx",
|
|
@@ -1190,12 +973,12 @@ var PLUGIN_REGISTRY = {
|
|
|
1190
973
|
"id": "email",
|
|
1191
974
|
"codeName": "email",
|
|
1192
975
|
"displayName": "Email",
|
|
1193
|
-
"description": "Send transactional emails
|
|
1194
|
-
"version": "1.0.0
|
|
976
|
+
"description": "Send transactional emails via Cloudflare Email Service. Subscribes to auth lifecycle events (welcome, password reset, password changed) and runs a 5-minute reconciliation cron against the CF GraphQL Activity Log.",
|
|
977
|
+
"version": "1.0.0",
|
|
1195
978
|
"author": "SonicJS Team",
|
|
1196
979
|
"category": "utilities",
|
|
1197
980
|
"iconEmoji": "\u{1F4E7}",
|
|
1198
|
-
"is_core":
|
|
981
|
+
"is_core": true,
|
|
1199
982
|
"permissions": [
|
|
1200
983
|
"email:manage",
|
|
1201
984
|
"email:send",
|
|
@@ -1203,19 +986,45 @@ var PLUGIN_REGISTRY = {
|
|
|
1203
986
|
],
|
|
1204
987
|
"dependencies": [],
|
|
1205
988
|
"defaultSettings": {
|
|
1206
|
-
"
|
|
989
|
+
"provider": "cloudflare",
|
|
990
|
+
"resendApiKey": "",
|
|
1207
991
|
"fromEmail": "",
|
|
1208
992
|
"fromName": "",
|
|
1209
993
|
"replyTo": "",
|
|
1210
|
-
"logoUrl": ""
|
|
994
|
+
"logoUrl": "",
|
|
995
|
+
"cfAccountId": "",
|
|
996
|
+
"cfEmailApiToken": ""
|
|
1211
997
|
},
|
|
1212
998
|
"adminMenu": {
|
|
1213
999
|
"label": "Email",
|
|
1214
1000
|
"icon": "envelope",
|
|
1215
|
-
"path": "/admin/plugins/email
|
|
1001
|
+
"path": "/admin/plugins/email",
|
|
1216
1002
|
"order": 80
|
|
1217
1003
|
}
|
|
1218
1004
|
},
|
|
1005
|
+
"forms": {
|
|
1006
|
+
"id": "forms",
|
|
1007
|
+
"codeName": "forms",
|
|
1008
|
+
"displayName": "Forms",
|
|
1009
|
+
"description": "Form builder with Form.io integration, Turnstile CAPTCHA support, and submission management",
|
|
1010
|
+
"version": "1.0.0",
|
|
1011
|
+
"author": "SonicJS Team",
|
|
1012
|
+
"category": "content",
|
|
1013
|
+
"iconEmoji": "\u{1F4CB}",
|
|
1014
|
+
"is_core": true,
|
|
1015
|
+
"permissions": [
|
|
1016
|
+
"forms:manage",
|
|
1017
|
+
"forms:view"
|
|
1018
|
+
],
|
|
1019
|
+
"dependencies": [],
|
|
1020
|
+
"defaultSettings": {},
|
|
1021
|
+
"adminMenu": {
|
|
1022
|
+
"label": "Forms",
|
|
1023
|
+
"icon": "document-text",
|
|
1024
|
+
"path": "/admin/forms",
|
|
1025
|
+
"order": 30
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1219
1028
|
"global-variables": {
|
|
1220
1029
|
"id": "global-variables",
|
|
1221
1030
|
"codeName": "global-variables",
|
|
@@ -1265,6 +1074,31 @@ var PLUGIN_REGISTRY = {
|
|
|
1265
1074
|
"order": 90
|
|
1266
1075
|
}
|
|
1267
1076
|
},
|
|
1077
|
+
"lexical-editor": {
|
|
1078
|
+
"id": "lexical-editor",
|
|
1079
|
+
"codeName": "lexical-editor",
|
|
1080
|
+
"displayName": "Lexical Rich Text Editor",
|
|
1081
|
+
"description": "Lexical editor integration for rich text editing. Default rich text editor for SonicJS \u2014 on by default for greenfield installs.",
|
|
1082
|
+
"version": "1.0.0",
|
|
1083
|
+
"author": "SonicJS Team",
|
|
1084
|
+
"category": "editor",
|
|
1085
|
+
"iconEmoji": "\u{1F4DD}",
|
|
1086
|
+
"is_core": true,
|
|
1087
|
+
"defaultActive": true,
|
|
1088
|
+
"permissions": [],
|
|
1089
|
+
"dependencies": [],
|
|
1090
|
+
"defaultSettings": {
|
|
1091
|
+
"defaultHeight": 300,
|
|
1092
|
+
"defaultToolbar": "standard",
|
|
1093
|
+
"placeholder": "Enter content..."
|
|
1094
|
+
},
|
|
1095
|
+
"adminMenu": {
|
|
1096
|
+
"label": "Lexical Editor",
|
|
1097
|
+
"icon": "pencil-square",
|
|
1098
|
+
"path": "/admin/plugins/lexical-editor",
|
|
1099
|
+
"order": 80
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1268
1102
|
"magic-link-auth": {
|
|
1269
1103
|
"id": "magic-link-auth",
|
|
1270
1104
|
"codeName": "magic-link-auth",
|
|
@@ -1286,6 +1120,33 @@ var PLUGIN_REGISTRY = {
|
|
|
1286
1120
|
},
|
|
1287
1121
|
"adminMenu": null
|
|
1288
1122
|
},
|
|
1123
|
+
"multi-tenant": {
|
|
1124
|
+
"id": "multi-tenant",
|
|
1125
|
+
"codeName": "multi-tenant",
|
|
1126
|
+
"displayName": "Multi-Tenant",
|
|
1127
|
+
"description": "Multi-tenancy for the document model: tenant registry, per-request tenant resolution (header, admin switcher cookie, or domain), and tenant-scoped content isolation. Off by default.",
|
|
1128
|
+
"version": "1.0.0",
|
|
1129
|
+
"author": "SonicJS Team",
|
|
1130
|
+
"category": "utilities",
|
|
1131
|
+
"iconEmoji": "\u{1F3E2}",
|
|
1132
|
+
"is_core": false,
|
|
1133
|
+
"permissions": [
|
|
1134
|
+
"tenants.manage",
|
|
1135
|
+
"tenants.view"
|
|
1136
|
+
],
|
|
1137
|
+
"dependencies": [],
|
|
1138
|
+
"defaultSettings": {
|
|
1139
|
+
"headerName": "X-Tenant-Id",
|
|
1140
|
+
"subdomainResolution": false,
|
|
1141
|
+
"rootDomain": ""
|
|
1142
|
+
},
|
|
1143
|
+
"adminMenu": {
|
|
1144
|
+
"label": "Tenants",
|
|
1145
|
+
"icon": "building-office",
|
|
1146
|
+
"path": "/admin/tenants",
|
|
1147
|
+
"order": 80
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1289
1150
|
"oauth-providers": {
|
|
1290
1151
|
"id": "oauth-providers",
|
|
1291
1152
|
"codeName": "oauth-providers",
|
|
@@ -1442,27 +1303,6 @@ var PLUGIN_REGISTRY = {
|
|
|
1442
1303
|
"order": 85
|
|
1443
1304
|
}
|
|
1444
1305
|
},
|
|
1445
|
-
"seed-data": {
|
|
1446
|
-
"id": "seed-data",
|
|
1447
|
-
"codeName": "seed-data",
|
|
1448
|
-
"displayName": "Seed Data Generator",
|
|
1449
|
-
"description": "Development tool for generating sample data and testing content. Useful for demos and development environments",
|
|
1450
|
-
"version": "1.0.0-beta.1",
|
|
1451
|
-
"author": "SonicJS Team",
|
|
1452
|
-
"category": "development",
|
|
1453
|
-
"iconEmoji": "\u{1F331}",
|
|
1454
|
-
"is_core": false,
|
|
1455
|
-
"permissions": [
|
|
1456
|
-
"seed-data:generate"
|
|
1457
|
-
],
|
|
1458
|
-
"dependencies": [],
|
|
1459
|
-
"defaultSettings": {
|
|
1460
|
-
"userCount": 20,
|
|
1461
|
-
"contentCount": 200,
|
|
1462
|
-
"defaultPassword": "password123"
|
|
1463
|
-
},
|
|
1464
|
-
"adminMenu": null
|
|
1465
|
-
},
|
|
1466
1306
|
"shortcodes": {
|
|
1467
1307
|
"id": "shortcodes",
|
|
1468
1308
|
"codeName": "shortcodes",
|
|
@@ -1515,23 +1355,6 @@ var PLUGIN_REGISTRY = {
|
|
|
1515
1355
|
"order": 90
|
|
1516
1356
|
}
|
|
1517
1357
|
},
|
|
1518
|
-
"testimonials-plugin": {
|
|
1519
|
-
"id": "testimonials-plugin",
|
|
1520
|
-
"codeName": "testimonials-plugin",
|
|
1521
|
-
"displayName": "Testimonials",
|
|
1522
|
-
"description": "Customer testimonials and reviews management with display widgets and ratings",
|
|
1523
|
-
"version": "1.0.0-beta.1",
|
|
1524
|
-
"author": "SonicJS Team",
|
|
1525
|
-
"category": "content",
|
|
1526
|
-
"iconEmoji": "\u{1F4AC}",
|
|
1527
|
-
"is_core": false,
|
|
1528
|
-
"permissions": [
|
|
1529
|
-
"testimonials:manage"
|
|
1530
|
-
],
|
|
1531
|
-
"dependencies": [],
|
|
1532
|
-
"defaultSettings": {},
|
|
1533
|
-
"adminMenu": null
|
|
1534
|
-
},
|
|
1535
1358
|
"tinymce-plugin": {
|
|
1536
1359
|
"id": "tinymce-plugin",
|
|
1537
1360
|
"codeName": "tinymce-plugin",
|
|
@@ -1600,27 +1423,19 @@ var PLUGIN_REGISTRY = {
|
|
|
1600
1423
|
"defaultSettings": {},
|
|
1601
1424
|
"adminMenu": null
|
|
1602
1425
|
},
|
|
1603
|
-
"
|
|
1604
|
-
"id": "
|
|
1605
|
-
"codeName": "
|
|
1606
|
-
"displayName": "
|
|
1607
|
-
"description": "
|
|
1608
|
-
"version": "1.0.0
|
|
1426
|
+
"versioning": {
|
|
1427
|
+
"id": "versioning",
|
|
1428
|
+
"codeName": "versioning",
|
|
1429
|
+
"displayName": "Versioning",
|
|
1430
|
+
"description": "View and restore content version history for types with versioning enabled.",
|
|
1431
|
+
"version": "1.0.0",
|
|
1609
1432
|
"author": "SonicJS Team",
|
|
1610
1433
|
"category": "content",
|
|
1611
|
-
"iconEmoji": "\u{
|
|
1612
|
-
"is_core":
|
|
1613
|
-
"permissions": [
|
|
1614
|
-
"workflow:manage",
|
|
1615
|
-
"workflow:approve"
|
|
1616
|
-
],
|
|
1434
|
+
"iconEmoji": "\u{1F551}",
|
|
1435
|
+
"is_core": true,
|
|
1436
|
+
"permissions": [],
|
|
1617
1437
|
"dependencies": [],
|
|
1618
|
-
"defaultSettings": {
|
|
1619
|
-
"enableApprovalChains": true,
|
|
1620
|
-
"enableScheduling": true,
|
|
1621
|
-
"enableAutomation": true,
|
|
1622
|
-
"enableNotifications": true
|
|
1623
|
-
},
|
|
1438
|
+
"defaultSettings": {},
|
|
1624
1439
|
"adminMenu": null
|
|
1625
1440
|
}
|
|
1626
1441
|
};
|
|
@@ -1635,17 +1450,8 @@ function findPluginByCodeName(codeName) {
|
|
|
1635
1450
|
// src/services/plugin-bootstrap.ts
|
|
1636
1451
|
var BOOTSTRAP_PLUGIN_IDS = [
|
|
1637
1452
|
"core-auth",
|
|
1638
|
-
|
|
1639
|
-
"
|
|
1640
|
-
"seed-data",
|
|
1641
|
-
"core-cache",
|
|
1642
|
-
"workflow-plugin",
|
|
1643
|
-
"easy-mdx",
|
|
1644
|
-
"ai-search",
|
|
1645
|
-
"oauth-providers",
|
|
1646
|
-
"global-variables",
|
|
1647
|
-
"user-profiles",
|
|
1648
|
-
"stripe"
|
|
1453
|
+
// Collect any registry entries marked defaultActive (e.g. lexical-editor)
|
|
1454
|
+
...Object.values(PLUGIN_REGISTRY).filter((e) => e.defaultActive === true && e.id !== "core-auth").map((e) => e.id)
|
|
1649
1455
|
];
|
|
1650
1456
|
function registryToCorePlugin(entry) {
|
|
1651
1457
|
return {
|
|
@@ -1670,9 +1476,10 @@ var PluginBootstrapService = class {
|
|
|
1670
1476
|
pluginService;
|
|
1671
1477
|
/**
|
|
1672
1478
|
* Core plugins derived from the auto-generated plugin registry.
|
|
1673
|
-
* Only plugins listed in BOOTSTRAP_PLUGIN_IDS are
|
|
1479
|
+
* Only plugins listed in BOOTSTRAP_PLUGIN_IDS AND marked is_core=true are auto-installed.
|
|
1480
|
+
* Non-core plugins are available in the registry but not bootstrapped.
|
|
1674
1481
|
*/
|
|
1675
|
-
CORE_PLUGINS = BOOTSTRAP_PLUGIN_IDS.filter((id) => PLUGIN_REGISTRY[id] !== void 0).map((id) => registryToCorePlugin(PLUGIN_REGISTRY[id]));
|
|
1482
|
+
CORE_PLUGINS = BOOTSTRAP_PLUGIN_IDS.filter((id) => PLUGIN_REGISTRY[id] !== void 0 && PLUGIN_REGISTRY[id].is_core === true).map((id) => registryToCorePlugin(PLUGIN_REGISTRY[id]));
|
|
1676
1483
|
/**
|
|
1677
1484
|
* Bootstrap all core plugins - install them if they don't exist
|
|
1678
1485
|
*/
|
|
@@ -1733,28 +1540,15 @@ var PluginBootstrapService = class {
|
|
|
1733
1540
|
}
|
|
1734
1541
|
}
|
|
1735
1542
|
/**
|
|
1736
|
-
* Update an existing plugin
|
|
1543
|
+
* Update an existing plugin's version/description/permissions/settings
|
|
1737
1544
|
*/
|
|
1738
1545
|
async updatePlugin(plugin) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
permissions = ?,
|
|
1746
|
-
settings = ?,
|
|
1747
|
-
last_updated = ?
|
|
1748
|
-
WHERE id = ?
|
|
1749
|
-
`);
|
|
1750
|
-
await stmt.bind(
|
|
1751
|
-
plugin.version,
|
|
1752
|
-
plugin.description,
|
|
1753
|
-
JSON.stringify(plugin.permissions),
|
|
1754
|
-
JSON.stringify(plugin.settings || {}),
|
|
1755
|
-
now,
|
|
1756
|
-
plugin.id
|
|
1757
|
-
).run();
|
|
1546
|
+
await this.pluginService.updatePluginVersion(plugin.id, {
|
|
1547
|
+
version: plugin.version,
|
|
1548
|
+
description: plugin.description,
|
|
1549
|
+
permissions: plugin.permissions,
|
|
1550
|
+
settings: plugin.settings || {}
|
|
1551
|
+
});
|
|
1758
1552
|
}
|
|
1759
1553
|
/**
|
|
1760
1554
|
* Check if bootstrap is needed (first run detection)
|
|
@@ -1780,27 +1574,280 @@ var PluginBootstrapService = class {
|
|
|
1780
1574
|
}
|
|
1781
1575
|
};
|
|
1782
1576
|
|
|
1577
|
+
// src/services/telemetry-service.ts
|
|
1578
|
+
var TelemetryService = class {
|
|
1579
|
+
config;
|
|
1580
|
+
identity = null;
|
|
1581
|
+
enabled = true;
|
|
1582
|
+
eventQueue = [];
|
|
1583
|
+
isInitialized = false;
|
|
1584
|
+
constructor(config) {
|
|
1585
|
+
this.config = {
|
|
1586
|
+
...chunkP3XDZL6Q_cjs.getTelemetryConfig(),
|
|
1587
|
+
...config
|
|
1588
|
+
};
|
|
1589
|
+
this.enabled = this.config.enabled;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Initialize the telemetry service
|
|
1593
|
+
*/
|
|
1594
|
+
async initialize(identity) {
|
|
1595
|
+
if (!this.enabled) {
|
|
1596
|
+
if (this.config.debug) {
|
|
1597
|
+
console.log("[Telemetry] Disabled via configuration");
|
|
1598
|
+
}
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
try {
|
|
1602
|
+
this.identity = identity;
|
|
1603
|
+
if (this.config.debug) {
|
|
1604
|
+
console.log("[Telemetry] Initialized with installation ID:", identity.installationId);
|
|
1605
|
+
}
|
|
1606
|
+
this.isInitialized = true;
|
|
1607
|
+
await this.flushQueue();
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
if (this.config.debug) {
|
|
1610
|
+
console.error("[Telemetry] Initialization failed:", error);
|
|
1611
|
+
}
|
|
1612
|
+
this.enabled = false;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Track a telemetry event
|
|
1617
|
+
*/
|
|
1618
|
+
async track(event, properties) {
|
|
1619
|
+
if (!this.enabled) return;
|
|
1620
|
+
try {
|
|
1621
|
+
const sanitizedProps = this.sanitizeProperties(properties);
|
|
1622
|
+
const enrichedProps = {
|
|
1623
|
+
...sanitizedProps,
|
|
1624
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1625
|
+
version: this.getVersion()
|
|
1626
|
+
};
|
|
1627
|
+
if (!this.isInitialized) {
|
|
1628
|
+
this.eventQueue.push({ event, properties: enrichedProps });
|
|
1629
|
+
if (this.config.debug) {
|
|
1630
|
+
console.log("[Telemetry] Queued event:", event, enrichedProps);
|
|
1631
|
+
}
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (this.identity && this.config.host) {
|
|
1635
|
+
const payload = {
|
|
1636
|
+
data: {
|
|
1637
|
+
installation_id: this.identity.installationId,
|
|
1638
|
+
event_type: event,
|
|
1639
|
+
properties: enrichedProps,
|
|
1640
|
+
timestamp: enrichedProps.timestamp
|
|
1641
|
+
}
|
|
1642
|
+
};
|
|
1643
|
+
fetch(`${this.config.host}/v1/events`, {
|
|
1644
|
+
method: "POST",
|
|
1645
|
+
headers: { "Content-Type": "application/json" },
|
|
1646
|
+
body: JSON.stringify(payload)
|
|
1647
|
+
}).catch(() => {
|
|
1648
|
+
});
|
|
1649
|
+
if (this.config.debug) {
|
|
1650
|
+
console.log("[Telemetry] Tracked event:", event, enrichedProps);
|
|
1651
|
+
}
|
|
1652
|
+
} else if (this.config.debug) {
|
|
1653
|
+
console.log("[Telemetry] Event (no endpoint):", event, enrichedProps);
|
|
1654
|
+
}
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
if (this.config.debug) {
|
|
1657
|
+
console.error("[Telemetry] Failed to track event:", error);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Track installation started
|
|
1663
|
+
*/
|
|
1664
|
+
async trackInstallationStarted(properties) {
|
|
1665
|
+
await this.track("installation_started", properties);
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Track installation completed
|
|
1669
|
+
*/
|
|
1670
|
+
async trackInstallationCompleted(properties) {
|
|
1671
|
+
await this.track("installation_completed", properties);
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Track installation failed
|
|
1675
|
+
*/
|
|
1676
|
+
async trackInstallationFailed(error, properties) {
|
|
1677
|
+
await this.track("installation_failed", {
|
|
1678
|
+
...properties,
|
|
1679
|
+
errorType: chunkP3XDZL6Q_cjs.sanitizeErrorMessage(error)
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Track dev server started
|
|
1684
|
+
*/
|
|
1685
|
+
async trackDevServerStarted(properties) {
|
|
1686
|
+
await this.track("dev_server_started", properties);
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Track page view in admin UI
|
|
1690
|
+
*/
|
|
1691
|
+
async trackPageView(route, properties) {
|
|
1692
|
+
await this.track("page_viewed", {
|
|
1693
|
+
...properties,
|
|
1694
|
+
route: chunkP3XDZL6Q_cjs.sanitizeRoute(route)
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Track error (sanitized)
|
|
1699
|
+
*/
|
|
1700
|
+
async trackError(error, properties) {
|
|
1701
|
+
await this.track("error_occurred", {
|
|
1702
|
+
...properties,
|
|
1703
|
+
errorType: chunkP3XDZL6Q_cjs.sanitizeErrorMessage(error)
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Track plugin activation
|
|
1708
|
+
*/
|
|
1709
|
+
async trackPluginActivated(properties) {
|
|
1710
|
+
await this.track("plugin_activated", properties);
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Track migration run
|
|
1714
|
+
*/
|
|
1715
|
+
async trackMigrationRun(properties) {
|
|
1716
|
+
await this.track("migration_run", properties);
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Track project snapshot — fired on bootstrap to capture runtime project shape.
|
|
1720
|
+
* Arrays serialized as JSON strings (sanitizeProperties strips objects/arrays).
|
|
1721
|
+
*/
|
|
1722
|
+
async trackProjectSnapshot(data) {
|
|
1723
|
+
if (!this.identity) {
|
|
1724
|
+
this.identity = { installationId: data.installation_id };
|
|
1725
|
+
this.isInitialized = true;
|
|
1726
|
+
}
|
|
1727
|
+
await this.track("project_snapshot", {
|
|
1728
|
+
installation_id: data.installation_id,
|
|
1729
|
+
collection_names: JSON.stringify(data.collection_names),
|
|
1730
|
+
collection_counts: JSON.stringify(data.collection_counts),
|
|
1731
|
+
active_plugins: JSON.stringify(data.active_plugins),
|
|
1732
|
+
field_type_histogram: JSON.stringify(data.field_type_histogram),
|
|
1733
|
+
doc_total: data.doc_total,
|
|
1734
|
+
sonicjs_version: data.sonicjs_version
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Flush queued events
|
|
1739
|
+
*/
|
|
1740
|
+
async flushQueue() {
|
|
1741
|
+
if (this.eventQueue.length === 0) return;
|
|
1742
|
+
const queue = [...this.eventQueue];
|
|
1743
|
+
this.eventQueue = [];
|
|
1744
|
+
for (const { event, properties } of queue) {
|
|
1745
|
+
await this.track(event, properties);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Sanitize properties to ensure no PII
|
|
1750
|
+
*/
|
|
1751
|
+
sanitizeProperties(properties) {
|
|
1752
|
+
if (!properties) return {};
|
|
1753
|
+
const sanitized = {};
|
|
1754
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
1755
|
+
if (value === void 0) continue;
|
|
1756
|
+
if (key === "route" && typeof value === "string") {
|
|
1757
|
+
sanitized[key] = chunkP3XDZL6Q_cjs.sanitizeRoute(value);
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
if (key.toLowerCase().includes("error") && typeof value === "string") {
|
|
1761
|
+
sanitized[key] = chunkP3XDZL6Q_cjs.sanitizeErrorMessage(value);
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1765
|
+
sanitized[key] = value;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return sanitized;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Get SonicJS version
|
|
1772
|
+
*/
|
|
1773
|
+
getVersion() {
|
|
1774
|
+
try {
|
|
1775
|
+
if (typeof process !== "undefined" && process.env) {
|
|
1776
|
+
return process.env.SONICJS_VERSION || "2.0.0";
|
|
1777
|
+
}
|
|
1778
|
+
return "2.0.0";
|
|
1779
|
+
} catch {
|
|
1780
|
+
return "unknown";
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Shutdown the telemetry service (no-op for fetch-based telemetry)
|
|
1785
|
+
*/
|
|
1786
|
+
async shutdown() {
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Enable telemetry
|
|
1790
|
+
*/
|
|
1791
|
+
enable() {
|
|
1792
|
+
this.enabled = true;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Disable telemetry
|
|
1796
|
+
*/
|
|
1797
|
+
disable() {
|
|
1798
|
+
this.enabled = false;
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Check if telemetry is enabled
|
|
1802
|
+
*/
|
|
1803
|
+
isEnabled() {
|
|
1804
|
+
return this.enabled;
|
|
1805
|
+
}
|
|
1806
|
+
};
|
|
1807
|
+
var telemetryInstance = null;
|
|
1808
|
+
function getTelemetryService(config) {
|
|
1809
|
+
if (!telemetryInstance) {
|
|
1810
|
+
telemetryInstance = new TelemetryService(config);
|
|
1811
|
+
}
|
|
1812
|
+
return telemetryInstance;
|
|
1813
|
+
}
|
|
1814
|
+
async function initTelemetry(identity, config) {
|
|
1815
|
+
const service = getTelemetryService(config);
|
|
1816
|
+
await service.initialize(identity);
|
|
1817
|
+
return service;
|
|
1818
|
+
}
|
|
1819
|
+
function createInstallationIdentity(projectName) {
|
|
1820
|
+
const installationId = chunkP3XDZL6Q_cjs.generateInstallationId();
|
|
1821
|
+
const identity = { installationId };
|
|
1822
|
+
if (projectName) {
|
|
1823
|
+
identity.projectId = chunkP3XDZL6Q_cjs.generateProjectId(projectName);
|
|
1824
|
+
}
|
|
1825
|
+
return identity;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
exports.CollectionRegistry = CollectionRegistry;
|
|
1829
|
+
exports.MULTI_TENANT_PLUGIN_ID = MULTI_TENANT_PLUGIN_ID;
|
|
1783
1830
|
exports.PLUGIN_REGISTRY = PLUGIN_REGISTRY;
|
|
1784
1831
|
exports.PluginBootstrapService = PluginBootstrapService;
|
|
1785
1832
|
exports.PluginService = PluginService;
|
|
1786
|
-
exports.
|
|
1787
|
-
exports.
|
|
1788
|
-
exports.
|
|
1789
|
-
exports.
|
|
1790
|
-
exports.deriveSubmissionTitle = deriveSubmissionTitle;
|
|
1833
|
+
exports.TENANT_COOKIE = TENANT_COOKIE;
|
|
1834
|
+
exports.TelemetryService = TelemetryService;
|
|
1835
|
+
exports.collectionRecordToRow = collectionRecordToRow;
|
|
1836
|
+
exports.createInstallationIdentity = createInstallationIdentity;
|
|
1791
1837
|
exports.findPluginByCodeName = findPluginByCodeName;
|
|
1792
|
-
exports.fullCollectionSync = fullCollectionSync;
|
|
1793
1838
|
exports.getAvailableCollectionNames = getAvailableCollectionNames;
|
|
1794
|
-
exports.
|
|
1795
|
-
exports.
|
|
1839
|
+
exports.getCollectionRegistry = getCollectionRegistry;
|
|
1840
|
+
exports.getTelemetryService = getTelemetryService;
|
|
1841
|
+
exports.getVisibleCollections = getVisibleCollections;
|
|
1842
|
+
exports.initTelemetry = initTelemetry;
|
|
1843
|
+
exports.invalidateTenantCache = invalidateTenantCache;
|
|
1844
|
+
exports.isCodeCollectionInternal = isCodeCollectionInternal;
|
|
1845
|
+
exports.isDbDocTypeInternal = isDbDocTypeInternal;
|
|
1796
1846
|
exports.loadCollectionConfig = loadCollectionConfig;
|
|
1797
1847
|
exports.loadCollectionConfigs = loadCollectionConfigs;
|
|
1798
|
-
exports.mapFormStatusToContentStatus = mapFormStatusToContentStatus;
|
|
1799
1848
|
exports.registerCollections = registerCollections;
|
|
1800
|
-
exports.
|
|
1801
|
-
exports.
|
|
1802
|
-
exports.syncCollections = syncCollections;
|
|
1803
|
-
exports.syncFormCollection = syncFormCollection;
|
|
1849
|
+
exports.resetCollectionRegistry = resetCollectionRegistry;
|
|
1850
|
+
exports.tenantMiddleware = tenantMiddleware;
|
|
1804
1851
|
exports.validateCollectionConfig = validateCollectionConfig;
|
|
1805
|
-
//# sourceMappingURL=chunk-
|
|
1806
|
-
//# sourceMappingURL=chunk-
|
|
1852
|
+
//# sourceMappingURL=chunk-R4ILO3W6.cjs.map
|
|
1853
|
+
//# sourceMappingURL=chunk-R4ILO3W6.cjs.map
|