@sonicjs-cms/core 2.18.1 → 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-XXDFQERJ.js → chunk-6OC6MF3C.js} +7192 -9806
- 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-IDCZBF35.js +1186 -0
- 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-SQ6FNXU2.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-MUNO67TT.cjs +1219 -0
- 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-MGFRZO24.js → chunk-QZGABF2M.js} +3 -149
- package/dist/chunk-QZGABF2M.js.map +1 -0
- package/dist/{chunk-T3Q5V33G.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-SXXTQETM.cjs → chunk-TO6EY4P7.cjs} +8722 -11323
- 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 +13737 -4327
- 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 +13456 -4068
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +38 -32
- package/dist/middleware.d.cts +69 -7
- package/dist/middleware.d.ts +69 -7
- package/dist/middleware.js +9 -3
- package/dist/migrations-2XHQEGOQ.cjs +13 -0
- package/dist/{migrations-IYNTWDC6.cjs.map → migrations-2XHQEGOQ.cjs.map} +1 -1
- package/dist/migrations-PE3CDVSM.js +4 -0
- package/dist/{migrations-R337UD46.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-4R3NOOL3.js +0 -2217
- package/dist/chunk-4R3NOOL3.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-ABB34XUS.cjs.map +0 -1
- package/dist/chunk-C54YUA23.cjs +0 -2219
- package/dist/chunk-C54YUA23.cjs.map +0 -1
- package/dist/chunk-DSUJ5YQH.cjs +0 -722
- package/dist/chunk-DSUJ5YQH.cjs.map +0 -1
- package/dist/chunk-EW5NOBVU.js +0 -1783
- package/dist/chunk-EW5NOBVU.js.map +0 -1
- package/dist/chunk-EXNEW5US.js +0 -648
- package/dist/chunk-EXNEW5US.js.map +0 -1
- package/dist/chunk-I2H5NGJQ.js +0 -692
- package/dist/chunk-I2H5NGJQ.js.map +0 -1
- package/dist/chunk-MGFRZO24.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-SQ6FNXU2.cjs.map +0 -1
- package/dist/chunk-SXXTQETM.cjs.map +0 -1
- package/dist/chunk-T3Q5V33G.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-XXDFQERJ.js.map +0 -1
- package/dist/migrations-IYNTWDC6.cjs +0 -13
- package/dist/migrations-R337UD46.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
package/dist/chunk-EW5NOBVU.js
DELETED
|
@@ -1,1783 +0,0 @@
|
|
|
1
|
-
// src/services/collection-loader.ts
|
|
2
|
-
var registeredCollections = [];
|
|
3
|
-
function registerCollections(collections) {
|
|
4
|
-
for (const config of collections) {
|
|
5
|
-
if (!config.name || !config.displayName || !config.schema) {
|
|
6
|
-
console.error(`Invalid collection config: missing required fields`, config);
|
|
7
|
-
continue;
|
|
8
|
-
}
|
|
9
|
-
const normalizedConfig = {
|
|
10
|
-
...config,
|
|
11
|
-
managed: config.managed !== void 0 ? config.managed : true,
|
|
12
|
-
isActive: config.isActive !== void 0 ? config.isActive : true
|
|
13
|
-
};
|
|
14
|
-
registeredCollections.push(normalizedConfig);
|
|
15
|
-
console.log(`\u2713 Registered collection: ${config.name}`);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
async function loadCollectionConfigs() {
|
|
19
|
-
const collections = [...registeredCollections];
|
|
20
|
-
if (registeredCollections.length > 0) {
|
|
21
|
-
console.log(`\u{1F4E6} Found ${registeredCollections.length} registered collection(s) from application`);
|
|
22
|
-
} else {
|
|
23
|
-
console.log(`\u26A0\uFE0F No collections registered. Make sure to call registerCollections() in your app's index.ts`);
|
|
24
|
-
console.log(` Example: import myCollection from './collections/my-collection.collection'`);
|
|
25
|
-
console.log(` registerCollections([myCollection])`);
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const modules = import.meta.glob?.("../collections/*.collection.ts", { eager: true }) || {};
|
|
29
|
-
let coreCollectionCount = 0;
|
|
30
|
-
for (const [path, module] of Object.entries(modules)) {
|
|
31
|
-
try {
|
|
32
|
-
const configModule = module;
|
|
33
|
-
if (!configModule.default) {
|
|
34
|
-
console.warn(`Collection file ${path} does not export a default config`);
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
const config = configModule.default;
|
|
38
|
-
if (!config.name || !config.displayName || !config.schema) {
|
|
39
|
-
console.error(`Invalid collection config in ${path}: missing required fields`);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const normalizedConfig = {
|
|
43
|
-
...config,
|
|
44
|
-
managed: config.managed !== void 0 ? config.managed : true,
|
|
45
|
-
isActive: config.isActive !== void 0 ? config.isActive : true
|
|
46
|
-
};
|
|
47
|
-
collections.push(normalizedConfig);
|
|
48
|
-
coreCollectionCount++;
|
|
49
|
-
console.log(`\u2713 Loaded core collection: ${config.name}`);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error(`Error loading collection from ${path}:`, error);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
console.log(`\u{1F4CA} Collection summary: ${collections.length} total (${registeredCollections.length} from app, ${coreCollectionCount} from core)`);
|
|
55
|
-
return collections;
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error("Error loading collection configurations:", error);
|
|
58
|
-
return collections;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async function loadCollectionConfig(name) {
|
|
62
|
-
try {
|
|
63
|
-
console.warn("loadCollectionConfig requires implementation in consuming application");
|
|
64
|
-
return null;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error(`Error loading collection ${name}:`, error);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async function getAvailableCollectionNames() {
|
|
71
|
-
try {
|
|
72
|
-
const modules = import.meta.glob?.("../collections/*.collection.ts") || {};
|
|
73
|
-
const names = [];
|
|
74
|
-
for (const path of Object.keys(modules)) {
|
|
75
|
-
const match = path.match(/\/([^/]+)\.collection\.ts$/);
|
|
76
|
-
if (match && match[1]) {
|
|
77
|
-
names.push(match[1]);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return names;
|
|
81
|
-
} catch (error) {
|
|
82
|
-
console.error("Error getting collection names:", error);
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function validateCollectionConfig(config) {
|
|
87
|
-
const errors = [];
|
|
88
|
-
if (!config.name) {
|
|
89
|
-
errors.push("Collection name is required");
|
|
90
|
-
} else if (!/^[a-z0-9_-]+$/.test(config.name)) {
|
|
91
|
-
errors.push("Collection name must contain only lowercase letters, numbers, underscores, and hyphens");
|
|
92
|
-
}
|
|
93
|
-
if (!config.displayName) {
|
|
94
|
-
errors.push("Display name is required");
|
|
95
|
-
}
|
|
96
|
-
if (!config.schema) {
|
|
97
|
-
errors.push("Schema is required");
|
|
98
|
-
} else {
|
|
99
|
-
if (config.schema.type !== "object") {
|
|
100
|
-
errors.push('Schema type must be "object"');
|
|
101
|
-
}
|
|
102
|
-
if (!config.schema.properties || typeof config.schema.properties !== "object") {
|
|
103
|
-
errors.push("Schema must have properties");
|
|
104
|
-
}
|
|
105
|
-
for (const [fieldName, fieldConfig] of Object.entries(config.schema.properties || {})) {
|
|
106
|
-
if (!fieldConfig.type) {
|
|
107
|
-
errors.push(`Field "${fieldName}" is missing type`);
|
|
108
|
-
}
|
|
109
|
-
if (fieldConfig.type === "reference" && !fieldConfig.collection) {
|
|
110
|
-
errors.push(`Reference field "${fieldName}" is missing collection property`);
|
|
111
|
-
}
|
|
112
|
-
const layoutValue = fieldConfig.objectLayout;
|
|
113
|
-
if (layoutValue !== void 0) {
|
|
114
|
-
if (fieldConfig.type !== "object") {
|
|
115
|
-
errors.push(`Field "${fieldName}" uses objectLayout but is not an object field`);
|
|
116
|
-
} else if (!["nested", "flat"].includes(layoutValue)) {
|
|
117
|
-
errors.push(`Object field "${fieldName}" has invalid objectLayout. Use "nested" or "flat"`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (["select", "multiselect", "radio"].includes(fieldConfig.type) && !fieldConfig.enum) {
|
|
121
|
-
errors.push(`Select field "${fieldName}" is missing enum options`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
valid: errors.length === 0,
|
|
127
|
-
errors
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// src/services/collection-sync.ts
|
|
132
|
-
async function syncCollections(db) {
|
|
133
|
-
console.log("\u{1F504} Starting collection sync...");
|
|
134
|
-
const results = [];
|
|
135
|
-
const configs = await loadCollectionConfigs();
|
|
136
|
-
if (configs.length === 0) {
|
|
137
|
-
console.log("\u26A0\uFE0F No collection configurations found");
|
|
138
|
-
return results;
|
|
139
|
-
}
|
|
140
|
-
for (const config of configs) {
|
|
141
|
-
const result = await syncCollection(db, config);
|
|
142
|
-
results.push(result);
|
|
143
|
-
}
|
|
144
|
-
const created = results.filter((r) => r.status === "created").length;
|
|
145
|
-
const updated = results.filter((r) => r.status === "updated").length;
|
|
146
|
-
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
147
|
-
const errors = results.filter((r) => r.status === "error").length;
|
|
148
|
-
console.log(`\u2705 Collection sync complete: ${created} created, ${updated} updated, ${unchanged} unchanged, ${errors} errors`);
|
|
149
|
-
return results;
|
|
150
|
-
}
|
|
151
|
-
async function syncCollection(db, config) {
|
|
152
|
-
try {
|
|
153
|
-
const validation = validateCollectionConfig(config);
|
|
154
|
-
if (!validation.valid) {
|
|
155
|
-
return {
|
|
156
|
-
name: config.name,
|
|
157
|
-
status: "error",
|
|
158
|
-
error: `Validation failed: ${validation.errors.join(", ")}`
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
const existingStmt = db.prepare("SELECT * FROM collections WHERE name = ?");
|
|
162
|
-
const existing = await existingStmt.bind(config.name).first();
|
|
163
|
-
const now = Date.now();
|
|
164
|
-
const collectionId = existing?.id || `col-${config.name}-${crypto.randomUUID().slice(0, 8)}`;
|
|
165
|
-
const schemaJson = JSON.stringify(config.schema);
|
|
166
|
-
const isActive = config.isActive !== false ? 1 : 0;
|
|
167
|
-
const managed = config.managed !== false ? 1 : 0;
|
|
168
|
-
if (!existing) {
|
|
169
|
-
const insertStmt = db.prepare(`
|
|
170
|
-
INSERT INTO collections (id, name, display_name, description, schema, is_active, managed, created_at, updated_at)
|
|
171
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
172
|
-
`);
|
|
173
|
-
await insertStmt.bind(
|
|
174
|
-
collectionId,
|
|
175
|
-
config.name,
|
|
176
|
-
config.displayName,
|
|
177
|
-
config.description || null,
|
|
178
|
-
schemaJson,
|
|
179
|
-
isActive,
|
|
180
|
-
managed,
|
|
181
|
-
now,
|
|
182
|
-
now
|
|
183
|
-
).run();
|
|
184
|
-
console.log(` \u2713 Created collection: ${config.name}`);
|
|
185
|
-
return {
|
|
186
|
-
name: config.name,
|
|
187
|
-
status: "created",
|
|
188
|
-
message: `Created collection "${config.displayName}"`
|
|
189
|
-
};
|
|
190
|
-
} else {
|
|
191
|
-
const existingSchema = existing.schema ? JSON.stringify(existing.schema) : "{}";
|
|
192
|
-
const existingDisplayName = existing.display_name;
|
|
193
|
-
const existingDescription = existing.description;
|
|
194
|
-
const existingIsActive = existing.is_active;
|
|
195
|
-
const existingManaged = existing.managed;
|
|
196
|
-
const needsUpdate = schemaJson !== existingSchema || config.displayName !== existingDisplayName || (config.description || null) !== existingDescription || isActive !== existingIsActive || managed !== existingManaged;
|
|
197
|
-
if (!needsUpdate) {
|
|
198
|
-
return {
|
|
199
|
-
name: config.name,
|
|
200
|
-
status: "unchanged",
|
|
201
|
-
message: `Collection "${config.displayName}" is up to date`
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
const updateStmt = db.prepare(`
|
|
205
|
-
UPDATE collections
|
|
206
|
-
SET display_name = ?, description = ?, schema = ?, is_active = ?, managed = ?, updated_at = ?
|
|
207
|
-
WHERE name = ?
|
|
208
|
-
`);
|
|
209
|
-
await updateStmt.bind(
|
|
210
|
-
config.displayName,
|
|
211
|
-
config.description || null,
|
|
212
|
-
schemaJson,
|
|
213
|
-
isActive,
|
|
214
|
-
managed,
|
|
215
|
-
now,
|
|
216
|
-
config.name
|
|
217
|
-
).run();
|
|
218
|
-
console.log(` \u2713 Updated collection: ${config.name}`);
|
|
219
|
-
return {
|
|
220
|
-
name: config.name,
|
|
221
|
-
status: "updated",
|
|
222
|
-
message: `Updated collection "${config.displayName}"`
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error(` \u2717 Error syncing collection ${config.name}:`, error);
|
|
227
|
-
return {
|
|
228
|
-
name: config.name,
|
|
229
|
-
status: "error",
|
|
230
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
async function isCollectionManaged(db, collectionName) {
|
|
235
|
-
try {
|
|
236
|
-
const stmt = db.prepare("SELECT managed FROM collections WHERE name = ?");
|
|
237
|
-
const result = await stmt.bind(collectionName).first();
|
|
238
|
-
return result?.managed === 1;
|
|
239
|
-
} catch (error) {
|
|
240
|
-
console.error(`Error checking if collection is managed:`, error);
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
async function getManagedCollections(db) {
|
|
245
|
-
try {
|
|
246
|
-
const stmt = db.prepare("SELECT name FROM collections WHERE managed = 1");
|
|
247
|
-
const { results } = await stmt.all();
|
|
248
|
-
return (results || []).map((row) => row.name);
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.error("Error getting managed collections:", error);
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
async function cleanupRemovedCollections(db) {
|
|
255
|
-
try {
|
|
256
|
-
const configs = await loadCollectionConfigs();
|
|
257
|
-
const configNames = new Set(configs.map((c) => c.name));
|
|
258
|
-
const managedCollections = await getManagedCollections(db);
|
|
259
|
-
const removed = [];
|
|
260
|
-
for (const managedName of managedCollections) {
|
|
261
|
-
if (!configNames.has(managedName)) {
|
|
262
|
-
const updateStmt = db.prepare(`
|
|
263
|
-
UPDATE collections
|
|
264
|
-
SET is_active = 0, updated_at = ?
|
|
265
|
-
WHERE name = ? AND managed = 1
|
|
266
|
-
`);
|
|
267
|
-
await updateStmt.bind(Date.now(), managedName).run();
|
|
268
|
-
removed.push(managedName);
|
|
269
|
-
console.log(` \u26A0\uFE0F Deactivated removed collection: ${managedName}`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return removed;
|
|
273
|
-
} catch (error) {
|
|
274
|
-
console.error("Error cleaning up removed collections:", error);
|
|
275
|
-
return [];
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
async function fullCollectionSync(db) {
|
|
279
|
-
const results = await syncCollections(db);
|
|
280
|
-
const removed = await cleanupRemovedCollections(db);
|
|
281
|
-
return { results, removed };
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// src/services/form-collection-sync.ts
|
|
285
|
-
var SYSTEM_FORM_USER_ID = "system-form-submission";
|
|
286
|
-
function mapFormioTypeToSchemaType(component) {
|
|
287
|
-
switch (component.type) {
|
|
288
|
-
case "textfield":
|
|
289
|
-
case "textarea":
|
|
290
|
-
case "password":
|
|
291
|
-
case "phoneNumber":
|
|
292
|
-
case "url":
|
|
293
|
-
return { type: "string", title: component.label || component.key };
|
|
294
|
-
case "email":
|
|
295
|
-
return { type: "string", format: "email", title: component.label || component.key };
|
|
296
|
-
case "number":
|
|
297
|
-
case "currency":
|
|
298
|
-
return { type: "number", title: component.label || component.key };
|
|
299
|
-
case "checkbox":
|
|
300
|
-
return { type: "boolean", title: component.label || component.key };
|
|
301
|
-
case "select":
|
|
302
|
-
case "radio": {
|
|
303
|
-
const enumValues = (component.data?.values || component.values || []).map((v) => v.value);
|
|
304
|
-
const enumLabels = (component.data?.values || component.values || []).map((v) => v.label);
|
|
305
|
-
return {
|
|
306
|
-
type: "select",
|
|
307
|
-
title: component.label || component.key,
|
|
308
|
-
enum: enumValues,
|
|
309
|
-
enumLabels
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
case "selectboxes":
|
|
313
|
-
return { type: "object", title: component.label || component.key };
|
|
314
|
-
case "datetime":
|
|
315
|
-
case "day":
|
|
316
|
-
case "time":
|
|
317
|
-
return { type: "string", format: "date-time", title: component.label || component.key };
|
|
318
|
-
case "file":
|
|
319
|
-
case "signature":
|
|
320
|
-
return { type: "string", title: component.label || component.key };
|
|
321
|
-
case "address":
|
|
322
|
-
return { type: "object", title: component.label || component.key };
|
|
323
|
-
case "hidden":
|
|
324
|
-
return { type: "string", title: component.label || component.key };
|
|
325
|
-
default:
|
|
326
|
-
return { type: "string", title: component.label || component.key };
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
function extractFieldComponents(components) {
|
|
330
|
-
const fields = [];
|
|
331
|
-
if (!components) return fields;
|
|
332
|
-
for (const comp of components) {
|
|
333
|
-
if (comp.type === "panel" || comp.type === "fieldset" || comp.type === "well" || comp.type === "tabs") {
|
|
334
|
-
if (comp.components) {
|
|
335
|
-
fields.push(...extractFieldComponents(comp.components));
|
|
336
|
-
}
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
if (comp.type === "columns" && comp.columns) {
|
|
340
|
-
for (const col of comp.columns) {
|
|
341
|
-
if (col.components) {
|
|
342
|
-
fields.push(...extractFieldComponents(col.components));
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
if (comp.type === "table" && comp.rows) {
|
|
348
|
-
for (const row of comp.rows) {
|
|
349
|
-
if (Array.isArray(row)) {
|
|
350
|
-
for (const cell of row) {
|
|
351
|
-
if (cell.components) {
|
|
352
|
-
fields.push(...extractFieldComponents(cell.components));
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
if (comp.type === "button" || comp.type === "htmlelement" || comp.type === "content") {
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (comp.type === "turnstile") {
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
if (comp.key) {
|
|
366
|
-
fields.push(comp);
|
|
367
|
-
}
|
|
368
|
-
if (comp.components) {
|
|
369
|
-
fields.push(...extractFieldComponents(comp.components));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return fields;
|
|
373
|
-
}
|
|
374
|
-
function deriveCollectionSchemaFromFormio(formioSchema) {
|
|
375
|
-
const components = formioSchema?.components || [];
|
|
376
|
-
const fieldComponents = extractFieldComponents(components);
|
|
377
|
-
const properties = {
|
|
378
|
-
// Always include a title field for the content item
|
|
379
|
-
title: { type: "string", title: "Title", required: true }
|
|
380
|
-
};
|
|
381
|
-
const required = ["title"];
|
|
382
|
-
for (const comp of fieldComponents) {
|
|
383
|
-
const key = comp.key;
|
|
384
|
-
if (!key || key === "submit" || key === "title") continue;
|
|
385
|
-
const fieldDef = mapFormioTypeToSchemaType(comp);
|
|
386
|
-
if (comp.validate?.required) {
|
|
387
|
-
fieldDef.required = true;
|
|
388
|
-
required.push(key);
|
|
389
|
-
}
|
|
390
|
-
properties[key] = fieldDef;
|
|
391
|
-
}
|
|
392
|
-
return { type: "object", properties, required };
|
|
393
|
-
}
|
|
394
|
-
function deriveSubmissionTitle(data, formDisplayName) {
|
|
395
|
-
const candidates = ["name", "fullName", "full_name", "firstName", "first_name"];
|
|
396
|
-
for (const key of candidates) {
|
|
397
|
-
if (data[key] && typeof data[key] === "string" && data[key].trim()) {
|
|
398
|
-
if (key === "firstName" || key === "first_name") {
|
|
399
|
-
const last = data["lastName"] || data["last_name"] || data["lastname"] || "";
|
|
400
|
-
if (last) return `${data[key].trim()} ${last.trim()}`;
|
|
401
|
-
}
|
|
402
|
-
return data[key].trim();
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (data.email && typeof data.email === "string" && data.email.trim()) {
|
|
406
|
-
return data.email.trim();
|
|
407
|
-
}
|
|
408
|
-
if (data.subject && typeof data.subject === "string" && data.subject.trim()) {
|
|
409
|
-
return data.subject.trim();
|
|
410
|
-
}
|
|
411
|
-
const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString("en-US", {
|
|
412
|
-
year: "numeric",
|
|
413
|
-
month: "short",
|
|
414
|
-
day: "numeric",
|
|
415
|
-
hour: "2-digit",
|
|
416
|
-
minute: "2-digit"
|
|
417
|
-
});
|
|
418
|
-
return `${formDisplayName} - ${dateStr}`;
|
|
419
|
-
}
|
|
420
|
-
function mapFormStatusToContentStatus(formStatus) {
|
|
421
|
-
switch (formStatus) {
|
|
422
|
-
case "pending":
|
|
423
|
-
return "published";
|
|
424
|
-
case "reviewed":
|
|
425
|
-
return "published";
|
|
426
|
-
case "approved":
|
|
427
|
-
return "published";
|
|
428
|
-
case "rejected":
|
|
429
|
-
return "archived";
|
|
430
|
-
case "spam":
|
|
431
|
-
return "deleted";
|
|
432
|
-
default:
|
|
433
|
-
return "published";
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
async function syncFormCollection(db, form) {
|
|
437
|
-
const collectionName = `form_${form.name}`;
|
|
438
|
-
const displayName = `${form.display_name} (Form)`;
|
|
439
|
-
const formioSchema = typeof form.formio_schema === "string" ? JSON.parse(form.formio_schema) : form.formio_schema;
|
|
440
|
-
const schema = deriveCollectionSchemaFromFormio(formioSchema);
|
|
441
|
-
const schemaJson = JSON.stringify(schema);
|
|
442
|
-
const now = Date.now();
|
|
443
|
-
const isActive = form.is_active ? 1 : 0;
|
|
444
|
-
const existing = await db.prepare(
|
|
445
|
-
"SELECT id, schema, display_name, description, is_active FROM collections WHERE source_type = ? AND source_id = ?"
|
|
446
|
-
).bind("form", form.id).first();
|
|
447
|
-
if (!existing) {
|
|
448
|
-
const collectionId = `col-form-${form.name}-${crypto.randomUUID().slice(0, 8)}`;
|
|
449
|
-
await db.prepare(`
|
|
450
|
-
INSERT INTO collections (id, name, display_name, description, schema, is_active, managed, source_type, source_id, created_at, updated_at)
|
|
451
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, 'form', ?, ?, ?)
|
|
452
|
-
`).bind(
|
|
453
|
-
collectionId,
|
|
454
|
-
collectionName,
|
|
455
|
-
displayName,
|
|
456
|
-
form.description || null,
|
|
457
|
-
schemaJson,
|
|
458
|
-
isActive,
|
|
459
|
-
form.id,
|
|
460
|
-
now,
|
|
461
|
-
now
|
|
462
|
-
).run();
|
|
463
|
-
console.log(`[FormSync] Created shadow collection: ${collectionName}`);
|
|
464
|
-
return { collectionId, status: "created" };
|
|
465
|
-
}
|
|
466
|
-
const existingSchema = existing.schema ? JSON.stringify(typeof existing.schema === "string" ? JSON.parse(existing.schema) : existing.schema) : "{}";
|
|
467
|
-
const needsUpdate = schemaJson !== existingSchema || displayName !== existing.display_name || (form.description || null) !== existing.description || isActive !== existing.is_active;
|
|
468
|
-
if (!needsUpdate) {
|
|
469
|
-
return { collectionId: existing.id, status: "unchanged" };
|
|
470
|
-
}
|
|
471
|
-
await db.prepare(`
|
|
472
|
-
UPDATE collections SET display_name = ?, description = ?, schema = ?, is_active = ?, updated_at = ?
|
|
473
|
-
WHERE id = ?
|
|
474
|
-
`).bind(
|
|
475
|
-
displayName,
|
|
476
|
-
form.description || null,
|
|
477
|
-
schemaJson,
|
|
478
|
-
isActive,
|
|
479
|
-
now,
|
|
480
|
-
existing.id
|
|
481
|
-
).run();
|
|
482
|
-
console.log(`[FormSync] Updated shadow collection: ${collectionName}`);
|
|
483
|
-
return { collectionId: existing.id, status: "updated" };
|
|
484
|
-
}
|
|
485
|
-
async function syncAllFormCollections(db) {
|
|
486
|
-
try {
|
|
487
|
-
const tableCheck = await db.prepare(
|
|
488
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name='forms'"
|
|
489
|
-
).first();
|
|
490
|
-
if (!tableCheck) {
|
|
491
|
-
console.log("[FormSync] Forms table does not exist, skipping form sync");
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
const { results: forms } = await db.prepare(
|
|
495
|
-
"SELECT id, name, display_name, description, formio_schema, is_active FROM forms"
|
|
496
|
-
).all();
|
|
497
|
-
if (!forms || forms.length === 0) {
|
|
498
|
-
console.log("[FormSync] No forms found, skipping");
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
let created = 0;
|
|
502
|
-
let updated = 0;
|
|
503
|
-
for (const form of forms) {
|
|
504
|
-
try {
|
|
505
|
-
const result = await syncFormCollection(db, form);
|
|
506
|
-
if (result.status === "created") created++;
|
|
507
|
-
if (result.status === "updated") updated++;
|
|
508
|
-
await backfillFormSubmissions(db, form.id, result.collectionId);
|
|
509
|
-
} catch (error) {
|
|
510
|
-
console.error(`[FormSync] Error syncing form ${form.name}:`, error);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
console.log(`[FormSync] Sync complete: ${created} created, ${updated} updated out of ${forms.length} forms`);
|
|
514
|
-
} catch (error) {
|
|
515
|
-
console.error("[FormSync] Error syncing form collections:", error);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
async function createContentFromSubmission(db, submissionData, form, submissionId, metadata = {}) {
|
|
519
|
-
try {
|
|
520
|
-
let collection = await db.prepare(
|
|
521
|
-
"SELECT id FROM collections WHERE source_type = ? AND source_id = ?"
|
|
522
|
-
).bind("form", form.id).first();
|
|
523
|
-
if (!collection) {
|
|
524
|
-
console.warn(`[FormSync] No shadow collection found for form ${form.name}, attempting to create...`);
|
|
525
|
-
try {
|
|
526
|
-
const fullForm = await db.prepare(
|
|
527
|
-
"SELECT id, name, display_name, description, formio_schema, is_active FROM forms WHERE id = ?"
|
|
528
|
-
).bind(form.id).first();
|
|
529
|
-
if (fullForm) {
|
|
530
|
-
const schema = typeof fullForm.formio_schema === "string" ? JSON.parse(fullForm.formio_schema) : fullForm.formio_schema;
|
|
531
|
-
const result = await syncFormCollection(db, {
|
|
532
|
-
id: fullForm.id,
|
|
533
|
-
name: fullForm.name,
|
|
534
|
-
display_name: fullForm.display_name,
|
|
535
|
-
description: fullForm.description,
|
|
536
|
-
formio_schema: schema,
|
|
537
|
-
is_active: fullForm.is_active ?? 1
|
|
538
|
-
});
|
|
539
|
-
collection = await db.prepare(
|
|
540
|
-
"SELECT id FROM collections WHERE source_type = ? AND source_id = ?"
|
|
541
|
-
).bind("form", form.id).first();
|
|
542
|
-
console.log(`[FormSync] On-the-fly sync result: ${result.status}, collectionId: ${result.collectionId}`);
|
|
543
|
-
}
|
|
544
|
-
} catch (syncErr) {
|
|
545
|
-
console.error("[FormSync] On-the-fly shadow collection creation failed:", syncErr);
|
|
546
|
-
}
|
|
547
|
-
if (!collection) {
|
|
548
|
-
console.error(`[FormSync] Still no shadow collection for form ${form.name} after recovery attempt`);
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const contentId = crypto.randomUUID();
|
|
553
|
-
const now = Date.now();
|
|
554
|
-
const title = deriveSubmissionTitle(submissionData, form.display_name);
|
|
555
|
-
const slug = `submission-${submissionId.slice(0, 8)}`;
|
|
556
|
-
const contentData = {
|
|
557
|
-
title,
|
|
558
|
-
...submissionData,
|
|
559
|
-
_submission_metadata: {
|
|
560
|
-
submissionId,
|
|
561
|
-
formId: form.id,
|
|
562
|
-
formName: form.name,
|
|
563
|
-
email: metadata.userEmail || submissionData.email || null,
|
|
564
|
-
ipAddress: metadata.ipAddress || null,
|
|
565
|
-
userAgent: metadata.userAgent || null,
|
|
566
|
-
submittedAt: now
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
const authorId = metadata.userId || SYSTEM_FORM_USER_ID;
|
|
570
|
-
if (authorId === SYSTEM_FORM_USER_ID) {
|
|
571
|
-
const systemUser = await db.prepare("SELECT id FROM users WHERE id = ?").bind(SYSTEM_FORM_USER_ID).first();
|
|
572
|
-
if (!systemUser) {
|
|
573
|
-
console.log("[FormSync] System form user missing, creating...");
|
|
574
|
-
const sysNow = Date.now();
|
|
575
|
-
await db.prepare(`
|
|
576
|
-
INSERT OR IGNORE INTO users (id, email, username, first_name, last_name, password_hash, role, is_active, created_at, updated_at)
|
|
577
|
-
VALUES (?, ?, ?, ?, ?, NULL, 'viewer', 0, ?, ?)
|
|
578
|
-
`).bind(SYSTEM_FORM_USER_ID, "system-forms@sonicjs.internal", "system-forms", "Form", "Submission", sysNow, sysNow).run();
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
console.log(`[FormSync] Inserting content: id=${contentId}, collection=${collection.id}, slug=${slug}, title=${title}, author=${authorId}`);
|
|
582
|
-
await db.prepare(`
|
|
583
|
-
INSERT INTO content (id, collection_id, slug, title, data, status, author_id, created_at, updated_at)
|
|
584
|
-
VALUES (?, ?, ?, ?, ?, 'published', ?, ?, ?)
|
|
585
|
-
`).bind(
|
|
586
|
-
contentId,
|
|
587
|
-
collection.id,
|
|
588
|
-
slug,
|
|
589
|
-
title,
|
|
590
|
-
JSON.stringify(contentData),
|
|
591
|
-
authorId,
|
|
592
|
-
now,
|
|
593
|
-
now
|
|
594
|
-
).run();
|
|
595
|
-
await db.prepare(
|
|
596
|
-
"UPDATE form_submissions SET content_id = ? WHERE id = ?"
|
|
597
|
-
).bind(contentId, submissionId).run();
|
|
598
|
-
console.log(`[FormSync] Content created successfully: ${contentId}`);
|
|
599
|
-
return contentId;
|
|
600
|
-
} catch (error) {
|
|
601
|
-
console.error("[FormSync] Error creating content from submission:", error);
|
|
602
|
-
return null;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
async function backfillFormSubmissions(db, formId, collectionId) {
|
|
606
|
-
try {
|
|
607
|
-
const { results: submissions } = await db.prepare(
|
|
608
|
-
"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"
|
|
609
|
-
).bind(formId).all();
|
|
610
|
-
if (!submissions || submissions.length === 0) {
|
|
611
|
-
return 0;
|
|
612
|
-
}
|
|
613
|
-
const form = await db.prepare(
|
|
614
|
-
"SELECT id, name, display_name FROM forms WHERE id = ?"
|
|
615
|
-
).bind(formId).first();
|
|
616
|
-
if (!form) return 0;
|
|
617
|
-
let count = 0;
|
|
618
|
-
for (const sub of submissions) {
|
|
619
|
-
try {
|
|
620
|
-
const submissionData = typeof sub.submission_data === "string" ? JSON.parse(sub.submission_data) : sub.submission_data;
|
|
621
|
-
const contentId = await createContentFromSubmission(
|
|
622
|
-
db,
|
|
623
|
-
submissionData,
|
|
624
|
-
{ id: form.id, name: form.name, display_name: form.display_name },
|
|
625
|
-
sub.id,
|
|
626
|
-
{
|
|
627
|
-
ipAddress: sub.ip_address,
|
|
628
|
-
userAgent: sub.user_agent,
|
|
629
|
-
userEmail: sub.user_email,
|
|
630
|
-
userId: sub.user_id
|
|
631
|
-
}
|
|
632
|
-
);
|
|
633
|
-
if (contentId) count++;
|
|
634
|
-
} catch (error) {
|
|
635
|
-
console.error(`[FormSync] Error backfilling submission ${sub.id}:`, error);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
if (count > 0) {
|
|
639
|
-
console.log(`[FormSync] Backfilled ${count} submissions for form ${formId}`);
|
|
640
|
-
}
|
|
641
|
-
return count;
|
|
642
|
-
} catch (error) {
|
|
643
|
-
console.error("[FormSync] Error backfilling submissions:", error);
|
|
644
|
-
return 0;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// src/services/plugin-service.ts
|
|
649
|
-
var PluginService = class {
|
|
650
|
-
constructor(db) {
|
|
651
|
-
this.db = db;
|
|
652
|
-
}
|
|
653
|
-
async getAllPlugins() {
|
|
654
|
-
await this.ensureAllPluginsExist();
|
|
655
|
-
const stmt = this.db.prepare(`
|
|
656
|
-
SELECT * FROM plugins
|
|
657
|
-
ORDER BY is_core DESC, display_name ASC
|
|
658
|
-
`);
|
|
659
|
-
const { results } = await stmt.all();
|
|
660
|
-
return (results || []).map(this.mapPluginFromDb);
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Ensure all plugins from the registry exist in the database
|
|
664
|
-
* Auto-installs any newly detected plugins with inactive status
|
|
665
|
-
*
|
|
666
|
-
* Note: This method should be overridden or configured with a plugin registry
|
|
667
|
-
* in the consuming application
|
|
668
|
-
*/
|
|
669
|
-
async ensureAllPluginsExist() {
|
|
670
|
-
console.log("[PluginService] ensureAllPluginsExist - requires PLUGIN_REGISTRY configuration");
|
|
671
|
-
}
|
|
672
|
-
async getPlugin(pluginId) {
|
|
673
|
-
const stmt = this.db.prepare("SELECT * FROM plugins WHERE id = ?");
|
|
674
|
-
const plugin = await stmt.bind(pluginId).first();
|
|
675
|
-
if (!plugin) return null;
|
|
676
|
-
return this.mapPluginFromDb(plugin);
|
|
677
|
-
}
|
|
678
|
-
async getPluginByName(name) {
|
|
679
|
-
const stmt = this.db.prepare("SELECT * FROM plugins WHERE name = ?");
|
|
680
|
-
const plugin = await stmt.bind(name).first();
|
|
681
|
-
if (!plugin) return null;
|
|
682
|
-
return this.mapPluginFromDb(plugin);
|
|
683
|
-
}
|
|
684
|
-
async getPluginStats() {
|
|
685
|
-
const stmt = this.db.prepare(`
|
|
686
|
-
SELECT
|
|
687
|
-
COUNT(*) as total,
|
|
688
|
-
COUNT(CASE WHEN status = 'active' THEN 1 END) as active,
|
|
689
|
-
COUNT(CASE WHEN status = 'inactive' THEN 1 END) as inactive,
|
|
690
|
-
COUNT(CASE WHEN status = 'error' THEN 1 END) as errors
|
|
691
|
-
FROM plugins
|
|
692
|
-
`);
|
|
693
|
-
const stats = await stmt.first();
|
|
694
|
-
return {
|
|
695
|
-
total: stats.total || 0,
|
|
696
|
-
active: stats.active || 0,
|
|
697
|
-
inactive: stats.inactive || 0,
|
|
698
|
-
errors: stats.errors || 0,
|
|
699
|
-
uninstalled: 0
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
async installPlugin(pluginData) {
|
|
703
|
-
const id = pluginData.id || `plugin-${Date.now()}`;
|
|
704
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
705
|
-
const stmt = this.db.prepare(`
|
|
706
|
-
INSERT INTO plugins (
|
|
707
|
-
id, name, display_name, description, version, author, category, icon,
|
|
708
|
-
status, is_core, settings, permissions, dependencies, download_count,
|
|
709
|
-
rating, installed_at, last_updated
|
|
710
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
711
|
-
`);
|
|
712
|
-
await stmt.bind(
|
|
713
|
-
id,
|
|
714
|
-
pluginData.name || id,
|
|
715
|
-
pluginData.display_name || "Unnamed Plugin",
|
|
716
|
-
pluginData.description || "",
|
|
717
|
-
pluginData.version || "1.0.0",
|
|
718
|
-
pluginData.author || "Unknown",
|
|
719
|
-
pluginData.category || "utilities",
|
|
720
|
-
pluginData.icon || "\u{1F50C}",
|
|
721
|
-
"inactive",
|
|
722
|
-
pluginData.is_core || false,
|
|
723
|
-
JSON.stringify(pluginData.settings || {}),
|
|
724
|
-
JSON.stringify(pluginData.permissions || []),
|
|
725
|
-
JSON.stringify(pluginData.dependencies || []),
|
|
726
|
-
pluginData.download_count || 0,
|
|
727
|
-
pluginData.rating || 0,
|
|
728
|
-
now,
|
|
729
|
-
now
|
|
730
|
-
).run();
|
|
731
|
-
await this.logActivity(id, "installed", null, { version: pluginData.version });
|
|
732
|
-
const installed = await this.getPlugin(id);
|
|
733
|
-
if (!installed) throw new Error("Failed to install plugin");
|
|
734
|
-
return installed;
|
|
735
|
-
}
|
|
736
|
-
async uninstallPlugin(pluginId) {
|
|
737
|
-
const plugin = await this.getPlugin(pluginId);
|
|
738
|
-
if (!plugin) throw new Error("Plugin not found");
|
|
739
|
-
if (plugin.is_core) throw new Error("Cannot uninstall core plugins");
|
|
740
|
-
if (plugin.status === "active") {
|
|
741
|
-
await this.deactivatePlugin(pluginId);
|
|
742
|
-
}
|
|
743
|
-
const stmt = this.db.prepare("DELETE FROM plugins WHERE id = ?");
|
|
744
|
-
await stmt.bind(pluginId).run();
|
|
745
|
-
await this.logActivity(pluginId, "uninstalled", null, { name: plugin.name });
|
|
746
|
-
}
|
|
747
|
-
async activatePlugin(pluginId) {
|
|
748
|
-
const plugin = await this.getPlugin(pluginId);
|
|
749
|
-
if (!plugin) throw new Error("Plugin not found");
|
|
750
|
-
if (plugin.dependencies && plugin.dependencies.length > 0) {
|
|
751
|
-
await this.checkDependencies(plugin.dependencies);
|
|
752
|
-
}
|
|
753
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
754
|
-
const stmt = this.db.prepare(`
|
|
755
|
-
UPDATE plugins
|
|
756
|
-
SET status = 'active', activated_at = ?, error_message = NULL
|
|
757
|
-
WHERE id = ?
|
|
758
|
-
`);
|
|
759
|
-
await stmt.bind(now, pluginId).run();
|
|
760
|
-
await this.logActivity(pluginId, "activated", null);
|
|
761
|
-
}
|
|
762
|
-
async deactivatePlugin(pluginId) {
|
|
763
|
-
const plugin = await this.getPlugin(pluginId);
|
|
764
|
-
if (!plugin) throw new Error("Plugin not found");
|
|
765
|
-
await this.checkDependents(plugin.name);
|
|
766
|
-
const stmt = this.db.prepare(`
|
|
767
|
-
UPDATE plugins
|
|
768
|
-
SET status = 'inactive', activated_at = NULL
|
|
769
|
-
WHERE id = ?
|
|
770
|
-
`);
|
|
771
|
-
await stmt.bind(pluginId).run();
|
|
772
|
-
await this.logActivity(pluginId, "deactivated", null);
|
|
773
|
-
}
|
|
774
|
-
async updatePluginSettings(pluginId, settings) {
|
|
775
|
-
const plugin = await this.getPlugin(pluginId);
|
|
776
|
-
if (!plugin) throw new Error("Plugin not found");
|
|
777
|
-
const stmt = this.db.prepare(`
|
|
778
|
-
UPDATE plugins
|
|
779
|
-
SET settings = ?, updated_at = unixepoch()
|
|
780
|
-
WHERE id = ?
|
|
781
|
-
`);
|
|
782
|
-
await stmt.bind(JSON.stringify(settings), pluginId).run();
|
|
783
|
-
await this.logActivity(pluginId, "settings_updated", null);
|
|
784
|
-
}
|
|
785
|
-
async setPluginError(pluginId, error) {
|
|
786
|
-
const stmt = this.db.prepare(`
|
|
787
|
-
UPDATE plugins
|
|
788
|
-
SET status = 'error', error_message = ?
|
|
789
|
-
WHERE id = ?
|
|
790
|
-
`);
|
|
791
|
-
await stmt.bind(error, pluginId).run();
|
|
792
|
-
await this.logActivity(pluginId, "error", null, { error });
|
|
793
|
-
}
|
|
794
|
-
async getPluginActivity(pluginId, limit = 10) {
|
|
795
|
-
const stmt = this.db.prepare(`
|
|
796
|
-
SELECT * FROM plugin_activity_log
|
|
797
|
-
WHERE plugin_id = ?
|
|
798
|
-
ORDER BY timestamp DESC
|
|
799
|
-
LIMIT ?
|
|
800
|
-
`);
|
|
801
|
-
const { results } = await stmt.bind(pluginId, limit).all();
|
|
802
|
-
return (results || []).map((row) => ({
|
|
803
|
-
id: row.id,
|
|
804
|
-
action: row.action,
|
|
805
|
-
userId: row.user_id,
|
|
806
|
-
details: row.details ? JSON.parse(row.details) : null,
|
|
807
|
-
timestamp: row.timestamp
|
|
808
|
-
}));
|
|
809
|
-
}
|
|
810
|
-
async registerHook(pluginId, hookName, handlerName, priority = 10) {
|
|
811
|
-
const id = `hook-${Date.now()}`;
|
|
812
|
-
const stmt = this.db.prepare(`
|
|
813
|
-
INSERT INTO plugin_hooks (id, plugin_id, hook_name, handler_name, priority)
|
|
814
|
-
VALUES (?, ?, ?, ?, ?)
|
|
815
|
-
`);
|
|
816
|
-
await stmt.bind(id, pluginId, hookName, handlerName, priority).run();
|
|
817
|
-
}
|
|
818
|
-
async registerRoute(pluginId, path, method, handlerName, middleware) {
|
|
819
|
-
const id = `route-${Date.now()}`;
|
|
820
|
-
const stmt = this.db.prepare(`
|
|
821
|
-
INSERT INTO plugin_routes (id, plugin_id, path, method, handler_name, middleware)
|
|
822
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
823
|
-
`);
|
|
824
|
-
await stmt.bind(
|
|
825
|
-
id,
|
|
826
|
-
pluginId,
|
|
827
|
-
path,
|
|
828
|
-
method,
|
|
829
|
-
handlerName,
|
|
830
|
-
JSON.stringify(middleware || [])
|
|
831
|
-
).run();
|
|
832
|
-
}
|
|
833
|
-
async getPluginHooks(pluginId) {
|
|
834
|
-
const stmt = this.db.prepare(`
|
|
835
|
-
SELECT * FROM plugin_hooks
|
|
836
|
-
WHERE plugin_id = ? AND is_active = TRUE
|
|
837
|
-
ORDER BY priority ASC
|
|
838
|
-
`);
|
|
839
|
-
const { results } = await stmt.bind(pluginId).all();
|
|
840
|
-
return results || [];
|
|
841
|
-
}
|
|
842
|
-
async getPluginRoutes(pluginId) {
|
|
843
|
-
const stmt = this.db.prepare(`
|
|
844
|
-
SELECT * FROM plugin_routes
|
|
845
|
-
WHERE plugin_id = ? AND is_active = TRUE
|
|
846
|
-
`);
|
|
847
|
-
const { results } = await stmt.bind(pluginId).all();
|
|
848
|
-
return results || [];
|
|
849
|
-
}
|
|
850
|
-
async checkDependencies(dependencies) {
|
|
851
|
-
for (const dep of dependencies) {
|
|
852
|
-
const plugin = await this.getPluginByName(dep);
|
|
853
|
-
if (!plugin || plugin.status !== "active") {
|
|
854
|
-
throw new Error(`Required dependency '${dep}' is not active`);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
async checkDependents(pluginName) {
|
|
859
|
-
const stmt = this.db.prepare(`
|
|
860
|
-
SELECT id, display_name FROM plugins
|
|
861
|
-
WHERE status = 'active'
|
|
862
|
-
AND dependencies LIKE ?
|
|
863
|
-
`);
|
|
864
|
-
const { results } = await stmt.bind(`%"${pluginName}"%`).all();
|
|
865
|
-
if (results && results.length > 0) {
|
|
866
|
-
const names = results.map((p) => p.display_name).join(", ");
|
|
867
|
-
throw new Error(`Cannot deactivate. The following plugins depend on this one: ${names}`);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
async logActivity(pluginId, action, userId, details) {
|
|
871
|
-
const id = `activity-${Date.now()}`;
|
|
872
|
-
const stmt = this.db.prepare(`
|
|
873
|
-
INSERT INTO plugin_activity_log (id, plugin_id, action, user_id, details)
|
|
874
|
-
VALUES (?, ?, ?, ?, ?)
|
|
875
|
-
`);
|
|
876
|
-
await stmt.bind(
|
|
877
|
-
id,
|
|
878
|
-
pluginId,
|
|
879
|
-
action,
|
|
880
|
-
userId,
|
|
881
|
-
details ? JSON.stringify(details) : null
|
|
882
|
-
).run();
|
|
883
|
-
}
|
|
884
|
-
mapPluginFromDb(row) {
|
|
885
|
-
return {
|
|
886
|
-
id: row.id,
|
|
887
|
-
name: row.name,
|
|
888
|
-
display_name: row.display_name,
|
|
889
|
-
description: row.description,
|
|
890
|
-
version: row.version,
|
|
891
|
-
author: row.author,
|
|
892
|
-
category: row.category,
|
|
893
|
-
icon: row.icon,
|
|
894
|
-
status: row.status,
|
|
895
|
-
is_core: row.is_core === 1,
|
|
896
|
-
settings: row.settings ? JSON.parse(row.settings) : void 0,
|
|
897
|
-
permissions: row.permissions ? JSON.parse(row.permissions) : void 0,
|
|
898
|
-
dependencies: row.dependencies ? JSON.parse(row.dependencies) : void 0,
|
|
899
|
-
download_count: row.download_count || 0,
|
|
900
|
-
rating: row.rating || 0,
|
|
901
|
-
installed_at: row.installed_at,
|
|
902
|
-
activated_at: row.activated_at,
|
|
903
|
-
last_updated: row.last_updated,
|
|
904
|
-
error_message: row.error_message
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
};
|
|
908
|
-
|
|
909
|
-
// src/plugins/manifest-registry.ts
|
|
910
|
-
var PLUGIN_REGISTRY = {
|
|
911
|
-
"ai-search": {
|
|
912
|
-
"id": "ai-search",
|
|
913
|
-
"codeName": "ai-search-plugin",
|
|
914
|
-
"displayName": "AI Search",
|
|
915
|
-
"description": "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
|
|
916
|
-
"version": "1.0.0",
|
|
917
|
-
"author": "SonicJS",
|
|
918
|
-
"category": "content",
|
|
919
|
-
"iconEmoji": "\u{1F50D}",
|
|
920
|
-
"is_core": true,
|
|
921
|
-
"permissions": [
|
|
922
|
-
"settings:write",
|
|
923
|
-
"admin:access",
|
|
924
|
-
"content:read"
|
|
925
|
-
],
|
|
926
|
-
"dependencies": [],
|
|
927
|
-
"defaultSettings": {
|
|
928
|
-
"enabled": true,
|
|
929
|
-
"ai_mode_enabled": true,
|
|
930
|
-
"selected_collections": [],
|
|
931
|
-
"dismissed_collections": [],
|
|
932
|
-
"autocomplete_enabled": true,
|
|
933
|
-
"cache_duration": 1,
|
|
934
|
-
"results_limit": 20,
|
|
935
|
-
"index_media": false
|
|
936
|
-
},
|
|
937
|
-
"adminMenu": {
|
|
938
|
-
"label": "AI Search",
|
|
939
|
-
"icon": "magnifying-glass",
|
|
940
|
-
"path": "/admin/plugins/ai-search",
|
|
941
|
-
"order": 50
|
|
942
|
-
}
|
|
943
|
-
},
|
|
944
|
-
"code-examples-plugin": {
|
|
945
|
-
"id": "code-examples-plugin",
|
|
946
|
-
"codeName": "code-examples-plugin",
|
|
947
|
-
"displayName": "Code Examples",
|
|
948
|
-
"description": "Code snippets and examples library with syntax highlighting and categorization",
|
|
949
|
-
"version": "1.0.0-beta.1",
|
|
950
|
-
"author": "SonicJS Team",
|
|
951
|
-
"category": "content",
|
|
952
|
-
"iconEmoji": "\u{1F4BB}",
|
|
953
|
-
"is_core": false,
|
|
954
|
-
"permissions": [
|
|
955
|
-
"code-examples:manage"
|
|
956
|
-
],
|
|
957
|
-
"dependencies": [],
|
|
958
|
-
"defaultSettings": {},
|
|
959
|
-
"adminMenu": null
|
|
960
|
-
},
|
|
961
|
-
"core-analytics": {
|
|
962
|
-
"id": "core-analytics",
|
|
963
|
-
"codeName": "core-analytics",
|
|
964
|
-
"displayName": "Analytics & Insights",
|
|
965
|
-
"description": "Core analytics system for tracking page views, user behavior, and content performance. Provides dashboards and reports with real-time metrics",
|
|
966
|
-
"version": "1.0.0-beta.1",
|
|
967
|
-
"author": "SonicJS Team",
|
|
968
|
-
"category": "seo",
|
|
969
|
-
"iconEmoji": "\u{1F4CA}",
|
|
970
|
-
"is_core": true,
|
|
971
|
-
"permissions": [
|
|
972
|
-
"analytics:view",
|
|
973
|
-
"analytics:export"
|
|
974
|
-
],
|
|
975
|
-
"dependencies": [],
|
|
976
|
-
"defaultSettings": {},
|
|
977
|
-
"adminMenu": {
|
|
978
|
-
"label": "Analytics",
|
|
979
|
-
"icon": "chart-bar",
|
|
980
|
-
"path": "/admin/analytics",
|
|
981
|
-
"order": 50
|
|
982
|
-
}
|
|
983
|
-
},
|
|
984
|
-
"core-auth": {
|
|
985
|
-
"id": "core-auth",
|
|
986
|
-
"codeName": "core-auth",
|
|
987
|
-
"displayName": "Authentication System",
|
|
988
|
-
"description": "Core authentication and user management system with role-based access control, session management, and security features",
|
|
989
|
-
"version": "1.0.0-beta.1",
|
|
990
|
-
"author": "SonicJS Team",
|
|
991
|
-
"category": "security",
|
|
992
|
-
"iconEmoji": "\u{1F510}",
|
|
993
|
-
"is_core": true,
|
|
994
|
-
"permissions": [
|
|
995
|
-
"manage:users",
|
|
996
|
-
"manage:roles",
|
|
997
|
-
"manage:permissions"
|
|
998
|
-
],
|
|
999
|
-
"dependencies": [],
|
|
1000
|
-
"defaultSettings": {
|
|
1001
|
-
"requiredFields": {
|
|
1002
|
-
"email": {
|
|
1003
|
-
"required": true,
|
|
1004
|
-
"minLength": 5,
|
|
1005
|
-
"label": "Email",
|
|
1006
|
-
"type": "email"
|
|
1007
|
-
},
|
|
1008
|
-
"password": {
|
|
1009
|
-
"required": true,
|
|
1010
|
-
"minLength": 8,
|
|
1011
|
-
"label": "Password",
|
|
1012
|
-
"type": "password"
|
|
1013
|
-
},
|
|
1014
|
-
"username": {
|
|
1015
|
-
"required": true,
|
|
1016
|
-
"minLength": 3,
|
|
1017
|
-
"label": "Username",
|
|
1018
|
-
"type": "text"
|
|
1019
|
-
},
|
|
1020
|
-
"firstName": {
|
|
1021
|
-
"required": true,
|
|
1022
|
-
"minLength": 1,
|
|
1023
|
-
"label": "First Name",
|
|
1024
|
-
"type": "text"
|
|
1025
|
-
},
|
|
1026
|
-
"lastName": {
|
|
1027
|
-
"required": true,
|
|
1028
|
-
"minLength": 1,
|
|
1029
|
-
"label": "Last Name",
|
|
1030
|
-
"type": "text"
|
|
1031
|
-
}
|
|
1032
|
-
},
|
|
1033
|
-
"validation": {
|
|
1034
|
-
"emailFormat": true,
|
|
1035
|
-
"allowDuplicateUsernames": false,
|
|
1036
|
-
"passwordRequirements": {
|
|
1037
|
-
"requireUppercase": false,
|
|
1038
|
-
"requireLowercase": false,
|
|
1039
|
-
"requireNumbers": false,
|
|
1040
|
-
"requireSpecialChars": false
|
|
1041
|
-
}
|
|
1042
|
-
},
|
|
1043
|
-
"registration": {
|
|
1044
|
-
"enabled": true,
|
|
1045
|
-
"requireEmailVerification": false,
|
|
1046
|
-
"defaultRole": "viewer"
|
|
1047
|
-
}
|
|
1048
|
-
},
|
|
1049
|
-
"adminMenu": null
|
|
1050
|
-
},
|
|
1051
|
-
"core-cache": {
|
|
1052
|
-
"id": "core-cache",
|
|
1053
|
-
"codeName": "core-cache",
|
|
1054
|
-
"displayName": "Cache System",
|
|
1055
|
-
"description": "Three-tiered caching system with in-memory and KV storage. Provides automatic caching for content, users, media, and API responses with configurable TTL and invalidation patterns.",
|
|
1056
|
-
"version": "1.0.0-beta.1",
|
|
1057
|
-
"author": "SonicJS",
|
|
1058
|
-
"category": "system",
|
|
1059
|
-
"iconEmoji": "\u26A1",
|
|
1060
|
-
"is_core": true,
|
|
1061
|
-
"permissions": [
|
|
1062
|
-
"cache.view",
|
|
1063
|
-
"cache.clear",
|
|
1064
|
-
"cache.invalidate"
|
|
1065
|
-
],
|
|
1066
|
-
"dependencies": [],
|
|
1067
|
-
"defaultSettings": {
|
|
1068
|
-
"enableMemoryCache": true,
|
|
1069
|
-
"enableKVCache": true,
|
|
1070
|
-
"enableDatabaseCache": true,
|
|
1071
|
-
"defaultTTL": 3600
|
|
1072
|
-
},
|
|
1073
|
-
"adminMenu": null
|
|
1074
|
-
},
|
|
1075
|
-
"core-media": {
|
|
1076
|
-
"id": "core-media",
|
|
1077
|
-
"codeName": "core-media",
|
|
1078
|
-
"displayName": "Media Manager",
|
|
1079
|
-
"description": "Core media upload and management system with support for images, videos, and documents. Includes automatic optimization, thumbnail generation, and cloud storage integration",
|
|
1080
|
-
"version": "1.0.0-beta.1",
|
|
1081
|
-
"author": "SonicJS Team",
|
|
1082
|
-
"category": "media",
|
|
1083
|
-
"iconEmoji": "\u{1F4F8}",
|
|
1084
|
-
"is_core": true,
|
|
1085
|
-
"permissions": [
|
|
1086
|
-
"manage:media",
|
|
1087
|
-
"upload:files"
|
|
1088
|
-
],
|
|
1089
|
-
"dependencies": [],
|
|
1090
|
-
"defaultSettings": {},
|
|
1091
|
-
"adminMenu": {
|
|
1092
|
-
"label": "Media",
|
|
1093
|
-
"icon": "image",
|
|
1094
|
-
"path": "/admin/media",
|
|
1095
|
-
"order": 30
|
|
1096
|
-
}
|
|
1097
|
-
},
|
|
1098
|
-
"database-tools": {
|
|
1099
|
-
"id": "database-tools",
|
|
1100
|
-
"codeName": "database-tools",
|
|
1101
|
-
"displayName": "Database Tools",
|
|
1102
|
-
"description": "Database management and administration tools including migrations, backups, and query execution",
|
|
1103
|
-
"version": "1.0.0-beta.1",
|
|
1104
|
-
"author": "SonicJS Team",
|
|
1105
|
-
"category": "development",
|
|
1106
|
-
"iconEmoji": "\u{1F5C4}\uFE0F",
|
|
1107
|
-
"is_core": false,
|
|
1108
|
-
"permissions": [
|
|
1109
|
-
"database:admin"
|
|
1110
|
-
],
|
|
1111
|
-
"dependencies": [],
|
|
1112
|
-
"defaultSettings": {
|
|
1113
|
-
"enableTruncate": true,
|
|
1114
|
-
"enableBackup": true,
|
|
1115
|
-
"enableValidation": true,
|
|
1116
|
-
"requireConfirmation": true
|
|
1117
|
-
},
|
|
1118
|
-
"adminMenu": null
|
|
1119
|
-
},
|
|
1120
|
-
"demo-login-plugin": {
|
|
1121
|
-
"id": "demo-login-plugin",
|
|
1122
|
-
"codeName": "demo-login-plugin",
|
|
1123
|
-
"displayName": "Demo Login",
|
|
1124
|
-
"description": "Quick demo login functionality for testing and demonstrations",
|
|
1125
|
-
"version": "1.0.0-beta.1",
|
|
1126
|
-
"author": "SonicJS Team",
|
|
1127
|
-
"category": "utilities",
|
|
1128
|
-
"iconEmoji": "\u{1F3AF}",
|
|
1129
|
-
"is_core": false,
|
|
1130
|
-
"permissions": [],
|
|
1131
|
-
"dependencies": [
|
|
1132
|
-
"core-auth"
|
|
1133
|
-
],
|
|
1134
|
-
"defaultSettings": {
|
|
1135
|
-
"enableNotice": true,
|
|
1136
|
-
"demoEmail": "admin@sonicjs.com",
|
|
1137
|
-
"demoPassword": "sonicjs!"
|
|
1138
|
-
},
|
|
1139
|
-
"adminMenu": null
|
|
1140
|
-
},
|
|
1141
|
-
"design": {
|
|
1142
|
-
"id": "design",
|
|
1143
|
-
"codeName": "design",
|
|
1144
|
-
"displayName": "Design System",
|
|
1145
|
-
"description": "Design system management including themes, components, and UI customization. Provides a visual interface for managing design tokens, typography, colors, and component library.",
|
|
1146
|
-
"version": "1.0.0-beta.1",
|
|
1147
|
-
"author": "SonicJS",
|
|
1148
|
-
"category": "utilities",
|
|
1149
|
-
"iconEmoji": "\u{1F3A8}",
|
|
1150
|
-
"is_core": false,
|
|
1151
|
-
"permissions": [
|
|
1152
|
-
"design.view",
|
|
1153
|
-
"design.edit"
|
|
1154
|
-
],
|
|
1155
|
-
"dependencies": [],
|
|
1156
|
-
"defaultSettings": {
|
|
1157
|
-
"defaultTheme": "light",
|
|
1158
|
-
"customCSS": ""
|
|
1159
|
-
},
|
|
1160
|
-
"adminMenu": {
|
|
1161
|
-
"label": "Design",
|
|
1162
|
-
"icon": "palette",
|
|
1163
|
-
"path": "/admin/design",
|
|
1164
|
-
"order": 80
|
|
1165
|
-
}
|
|
1166
|
-
},
|
|
1167
|
-
"easy-mdx": {
|
|
1168
|
-
"id": "easy-mdx",
|
|
1169
|
-
"codeName": "easy-mdx",
|
|
1170
|
-
"displayName": "EasyMDE Markdown Editor",
|
|
1171
|
-
"description": "Lightweight markdown editor with live preview. Provides a simple and efficient editor with markdown support for richtext fields.",
|
|
1172
|
-
"version": "1.0.0",
|
|
1173
|
-
"author": "SonicJS Team",
|
|
1174
|
-
"category": "editor",
|
|
1175
|
-
"iconEmoji": "\u{1F4DD}",
|
|
1176
|
-
"is_core": false,
|
|
1177
|
-
"permissions": [],
|
|
1178
|
-
"dependencies": [],
|
|
1179
|
-
"defaultSettings": {
|
|
1180
|
-
"defaultHeight": 400,
|
|
1181
|
-
"theme": "dark",
|
|
1182
|
-
"toolbar": "full",
|
|
1183
|
-
"placeholder": "Start writing your content..."
|
|
1184
|
-
},
|
|
1185
|
-
"adminMenu": null
|
|
1186
|
-
},
|
|
1187
|
-
"email": {
|
|
1188
|
-
"id": "email",
|
|
1189
|
-
"codeName": "email",
|
|
1190
|
-
"displayName": "Email",
|
|
1191
|
-
"description": "Send transactional emails using Resend",
|
|
1192
|
-
"version": "1.0.0-beta.1",
|
|
1193
|
-
"author": "SonicJS Team",
|
|
1194
|
-
"category": "utilities",
|
|
1195
|
-
"iconEmoji": "\u{1F4E7}",
|
|
1196
|
-
"is_core": false,
|
|
1197
|
-
"permissions": [
|
|
1198
|
-
"email:manage",
|
|
1199
|
-
"email:send",
|
|
1200
|
-
"email:view-logs"
|
|
1201
|
-
],
|
|
1202
|
-
"dependencies": [],
|
|
1203
|
-
"defaultSettings": {
|
|
1204
|
-
"apiKey": "",
|
|
1205
|
-
"fromEmail": "",
|
|
1206
|
-
"fromName": "",
|
|
1207
|
-
"replyTo": "",
|
|
1208
|
-
"logoUrl": ""
|
|
1209
|
-
},
|
|
1210
|
-
"adminMenu": {
|
|
1211
|
-
"label": "Email",
|
|
1212
|
-
"icon": "envelope",
|
|
1213
|
-
"path": "/admin/plugins/email/settings",
|
|
1214
|
-
"order": 80
|
|
1215
|
-
}
|
|
1216
|
-
},
|
|
1217
|
-
"global-variables": {
|
|
1218
|
-
"id": "global-variables",
|
|
1219
|
-
"codeName": "global-variables",
|
|
1220
|
-
"displayName": "Global Variables",
|
|
1221
|
-
"description": "Dynamic content variables with inline token support. Manage key-value variables via admin UI and use {variable_key} syntax in rich text fields for server-side resolution. Includes full CRUD admin page.",
|
|
1222
|
-
"version": "1.1.0",
|
|
1223
|
-
"author": "SonicJS Community",
|
|
1224
|
-
"category": "content",
|
|
1225
|
-
"iconEmoji": "\u{1F524}",
|
|
1226
|
-
"is_core": false,
|
|
1227
|
-
"permissions": [
|
|
1228
|
-
"global-variables:manage",
|
|
1229
|
-
"global-variables:view"
|
|
1230
|
-
],
|
|
1231
|
-
"dependencies": [],
|
|
1232
|
-
"defaultSettings": {
|
|
1233
|
-
"enableResolution": true,
|
|
1234
|
-
"cacheEnabled": true,
|
|
1235
|
-
"cacheTTL": 300
|
|
1236
|
-
},
|
|
1237
|
-
"adminMenu": {
|
|
1238
|
-
"label": "Global Variables",
|
|
1239
|
-
"icon": "variable",
|
|
1240
|
-
"path": "/admin/global-variables",
|
|
1241
|
-
"order": 45
|
|
1242
|
-
}
|
|
1243
|
-
},
|
|
1244
|
-
"hello-world": {
|
|
1245
|
-
"id": "hello-world",
|
|
1246
|
-
"codeName": "hello-world",
|
|
1247
|
-
"displayName": "Hello World",
|
|
1248
|
-
"description": "A simple Hello World plugin demonstration",
|
|
1249
|
-
"version": "1.0.0-beta.1",
|
|
1250
|
-
"author": "SonicJS Team",
|
|
1251
|
-
"category": "utilities",
|
|
1252
|
-
"iconEmoji": "\u{1F44B}",
|
|
1253
|
-
"is_core": false,
|
|
1254
|
-
"permissions": [
|
|
1255
|
-
"hello-world:view"
|
|
1256
|
-
],
|
|
1257
|
-
"dependencies": [],
|
|
1258
|
-
"defaultSettings": {},
|
|
1259
|
-
"adminMenu": {
|
|
1260
|
-
"label": "Hello World",
|
|
1261
|
-
"icon": "hand-raised",
|
|
1262
|
-
"path": "/admin/hello-world",
|
|
1263
|
-
"order": 90
|
|
1264
|
-
}
|
|
1265
|
-
},
|
|
1266
|
-
"magic-link-auth": {
|
|
1267
|
-
"id": "magic-link-auth",
|
|
1268
|
-
"codeName": "magic-link-auth",
|
|
1269
|
-
"displayName": "Magic Link Authentication",
|
|
1270
|
-
"description": "Passwordless authentication via email magic links. Users receive a secure one-time link to sign in without entering a password.",
|
|
1271
|
-
"version": "1.0.0",
|
|
1272
|
-
"author": "SonicJS Team",
|
|
1273
|
-
"category": "security",
|
|
1274
|
-
"iconEmoji": "\u{1F517}",
|
|
1275
|
-
"is_core": false,
|
|
1276
|
-
"permissions": [],
|
|
1277
|
-
"dependencies": [
|
|
1278
|
-
"email"
|
|
1279
|
-
],
|
|
1280
|
-
"defaultSettings": {
|
|
1281
|
-
"linkExpiryMinutes": 15,
|
|
1282
|
-
"rateLimitPerHour": 5,
|
|
1283
|
-
"allowNewUsers": true
|
|
1284
|
-
},
|
|
1285
|
-
"adminMenu": null
|
|
1286
|
-
},
|
|
1287
|
-
"oauth-providers": {
|
|
1288
|
-
"id": "oauth-providers",
|
|
1289
|
-
"codeName": "oauth-providers",
|
|
1290
|
-
"displayName": "OAuth Providers",
|
|
1291
|
-
"description": "OAuth2/OIDC social login with GitHub, Google, and more",
|
|
1292
|
-
"version": "1.0.0-beta.1",
|
|
1293
|
-
"author": "SonicJS Team",
|
|
1294
|
-
"category": "authentication",
|
|
1295
|
-
"iconEmoji": "\u{1F511}",
|
|
1296
|
-
"is_core": true,
|
|
1297
|
-
"permissions": [],
|
|
1298
|
-
"dependencies": [],
|
|
1299
|
-
"defaultSettings": {
|
|
1300
|
-
"providers": {
|
|
1301
|
-
"github": {
|
|
1302
|
-
"clientId": "",
|
|
1303
|
-
"clientSecret": "",
|
|
1304
|
-
"enabled": false
|
|
1305
|
-
},
|
|
1306
|
-
"google": {
|
|
1307
|
-
"clientId": "",
|
|
1308
|
-
"clientSecret": "",
|
|
1309
|
-
"enabled": false
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
},
|
|
1313
|
-
"adminMenu": null
|
|
1314
|
-
},
|
|
1315
|
-
"otp-login": {
|
|
1316
|
-
"id": "otp-login",
|
|
1317
|
-
"codeName": "otp-login",
|
|
1318
|
-
"displayName": "OTP Login",
|
|
1319
|
-
"description": "Passwordless authentication via email one-time codes",
|
|
1320
|
-
"version": "1.0.0-beta.1",
|
|
1321
|
-
"author": "SonicJS Team",
|
|
1322
|
-
"category": "security",
|
|
1323
|
-
"iconEmoji": "\u{1F522}",
|
|
1324
|
-
"is_core": false,
|
|
1325
|
-
"permissions": [
|
|
1326
|
-
"otp:manage",
|
|
1327
|
-
"otp:request",
|
|
1328
|
-
"otp:verify"
|
|
1329
|
-
],
|
|
1330
|
-
"dependencies": [
|
|
1331
|
-
"email"
|
|
1332
|
-
],
|
|
1333
|
-
"defaultSettings": {
|
|
1334
|
-
"codeLength": 6,
|
|
1335
|
-
"codeExpiryMinutes": 10,
|
|
1336
|
-
"maxAttempts": 3,
|
|
1337
|
-
"rateLimitPerHour": 5,
|
|
1338
|
-
"allowNewUserRegistration": false,
|
|
1339
|
-
"logoUrl": "",
|
|
1340
|
-
"logoWidth": 150,
|
|
1341
|
-
"logoBorderWidth": 0,
|
|
1342
|
-
"logoBorderColor": "#ffffff",
|
|
1343
|
-
"loginUrl": "",
|
|
1344
|
-
"loginButtonText": ""
|
|
1345
|
-
},
|
|
1346
|
-
"adminMenu": {
|
|
1347
|
-
"label": "OTP Login",
|
|
1348
|
-
"icon": "key",
|
|
1349
|
-
"path": "/admin/plugins/otp-login/settings",
|
|
1350
|
-
"order": 85
|
|
1351
|
-
}
|
|
1352
|
-
},
|
|
1353
|
-
"quill-editor": {
|
|
1354
|
-
"id": "quill-editor",
|
|
1355
|
-
"codeName": "quill-editor",
|
|
1356
|
-
"displayName": "Quill Rich Text Editor",
|
|
1357
|
-
"description": "Quill WYSIWYG editor integration for rich text editing. Lightweight, modern editor with customizable toolbars and dark mode support.",
|
|
1358
|
-
"version": "1.0.0",
|
|
1359
|
-
"author": "SonicJS Team",
|
|
1360
|
-
"category": "editor",
|
|
1361
|
-
"iconEmoji": "\u270D\uFE0F",
|
|
1362
|
-
"is_core": true,
|
|
1363
|
-
"permissions": [],
|
|
1364
|
-
"dependencies": [],
|
|
1365
|
-
"defaultSettings": {
|
|
1366
|
-
"version": "2.0.2",
|
|
1367
|
-
"defaultHeight": 300,
|
|
1368
|
-
"defaultToolbar": "full",
|
|
1369
|
-
"theme": "snow"
|
|
1370
|
-
},
|
|
1371
|
-
"adminMenu": null
|
|
1372
|
-
},
|
|
1373
|
-
"redirect-management": {
|
|
1374
|
-
"id": "redirect-management",
|
|
1375
|
-
"codeName": "redirect-management",
|
|
1376
|
-
"displayName": "Redirect Management",
|
|
1377
|
-
"description": "URL redirect management with exact, partial, and regex matching",
|
|
1378
|
-
"version": "1.0.0",
|
|
1379
|
-
"author": "ahaas",
|
|
1380
|
-
"category": "utilities",
|
|
1381
|
-
"iconEmoji": "\u21AA\uFE0F",
|
|
1382
|
-
"is_core": false,
|
|
1383
|
-
"permissions": [
|
|
1384
|
-
"redirect.manage",
|
|
1385
|
-
"redirect.view"
|
|
1386
|
-
],
|
|
1387
|
-
"dependencies": [],
|
|
1388
|
-
"defaultSettings": {
|
|
1389
|
-
"enabled": true,
|
|
1390
|
-
"autoOffloadEnabled": false
|
|
1391
|
-
},
|
|
1392
|
-
"adminMenu": {
|
|
1393
|
-
"label": "Redirects",
|
|
1394
|
-
"icon": "arrow-right",
|
|
1395
|
-
"path": "/admin/redirects",
|
|
1396
|
-
"order": 85
|
|
1397
|
-
}
|
|
1398
|
-
},
|
|
1399
|
-
"security-audit": {
|
|
1400
|
-
"id": "security-audit",
|
|
1401
|
-
"codeName": "security-audit",
|
|
1402
|
-
"displayName": "Security Audit",
|
|
1403
|
-
"description": "Security event logging, brute-force detection, and analytics dashboard. Monitors login attempts, registrations, lockouts, and suspicious activity.",
|
|
1404
|
-
"version": "1.0.0-beta.1",
|
|
1405
|
-
"author": "SonicJS Team",
|
|
1406
|
-
"category": "security",
|
|
1407
|
-
"iconEmoji": "\u{1F6E1}\uFE0F",
|
|
1408
|
-
"is_core": false,
|
|
1409
|
-
"permissions": [
|
|
1410
|
-
"security-audit:view",
|
|
1411
|
-
"security-audit:manage"
|
|
1412
|
-
],
|
|
1413
|
-
"dependencies": [],
|
|
1414
|
-
"defaultSettings": {
|
|
1415
|
-
"retention": {
|
|
1416
|
-
"daysToKeep": 90,
|
|
1417
|
-
"maxEvents": 1e5,
|
|
1418
|
-
"autoPurge": true
|
|
1419
|
-
},
|
|
1420
|
-
"bruteForce": {
|
|
1421
|
-
"enabled": true,
|
|
1422
|
-
"maxFailedAttemptsPerIP": 10,
|
|
1423
|
-
"maxFailedAttemptsPerEmail": 5,
|
|
1424
|
-
"windowMinutes": 15,
|
|
1425
|
-
"lockoutDurationMinutes": 30,
|
|
1426
|
-
"alertThreshold": 20
|
|
1427
|
-
},
|
|
1428
|
-
"logging": {
|
|
1429
|
-
"logSuccessfulLogins": true,
|
|
1430
|
-
"logLogouts": true,
|
|
1431
|
-
"logRegistrations": true,
|
|
1432
|
-
"logPasswordResets": true,
|
|
1433
|
-
"logPermissionDenied": true
|
|
1434
|
-
}
|
|
1435
|
-
},
|
|
1436
|
-
"adminMenu": {
|
|
1437
|
-
"label": "Security Audit",
|
|
1438
|
-
"icon": "shield-check",
|
|
1439
|
-
"path": "/admin/plugins/security-audit",
|
|
1440
|
-
"order": 85
|
|
1441
|
-
}
|
|
1442
|
-
},
|
|
1443
|
-
"seed-data": {
|
|
1444
|
-
"id": "seed-data",
|
|
1445
|
-
"codeName": "seed-data",
|
|
1446
|
-
"displayName": "Seed Data Generator",
|
|
1447
|
-
"description": "Development tool for generating sample data and testing content. Useful for demos and development environments",
|
|
1448
|
-
"version": "1.0.0-beta.1",
|
|
1449
|
-
"author": "SonicJS Team",
|
|
1450
|
-
"category": "development",
|
|
1451
|
-
"iconEmoji": "\u{1F331}",
|
|
1452
|
-
"is_core": false,
|
|
1453
|
-
"permissions": [
|
|
1454
|
-
"seed-data:generate"
|
|
1455
|
-
],
|
|
1456
|
-
"dependencies": [],
|
|
1457
|
-
"defaultSettings": {
|
|
1458
|
-
"userCount": 20,
|
|
1459
|
-
"contentCount": 200,
|
|
1460
|
-
"defaultPassword": "password123"
|
|
1461
|
-
},
|
|
1462
|
-
"adminMenu": null
|
|
1463
|
-
},
|
|
1464
|
-
"shortcodes": {
|
|
1465
|
-
"id": "shortcodes",
|
|
1466
|
-
"codeName": "shortcodes",
|
|
1467
|
-
"displayName": "Shortcodes",
|
|
1468
|
-
"description": 'Registered shortcode functions for dynamic content. Use [[shortcode_name param="value"]] syntax in rich text fields for server-side resolution. Includes handler registry, CRUD admin, and live preview.',
|
|
1469
|
-
"version": "1.0.0",
|
|
1470
|
-
"author": "SonicJS Community",
|
|
1471
|
-
"category": "content",
|
|
1472
|
-
"iconEmoji": "",
|
|
1473
|
-
"is_core": false,
|
|
1474
|
-
"permissions": [
|
|
1475
|
-
"shortcodes:manage",
|
|
1476
|
-
"shortcodes:view"
|
|
1477
|
-
],
|
|
1478
|
-
"dependencies": [],
|
|
1479
|
-
"defaultSettings": {},
|
|
1480
|
-
"adminMenu": {
|
|
1481
|
-
"label": "Shortcodes",
|
|
1482
|
-
"icon": "bolt",
|
|
1483
|
-
"path": "/admin/shortcodes",
|
|
1484
|
-
"order": 46
|
|
1485
|
-
}
|
|
1486
|
-
},
|
|
1487
|
-
"stripe": {
|
|
1488
|
-
"id": "stripe",
|
|
1489
|
-
"codeName": "stripe",
|
|
1490
|
-
"displayName": "Stripe Subscriptions",
|
|
1491
|
-
"description": "Stripe subscription management with webhook handling, checkout sessions, and subscription gating",
|
|
1492
|
-
"version": "1.0.0-beta.1",
|
|
1493
|
-
"author": "SonicJS Team",
|
|
1494
|
-
"category": "payments",
|
|
1495
|
-
"iconEmoji": "\u{1F4B3}",
|
|
1496
|
-
"is_core": true,
|
|
1497
|
-
"permissions": [
|
|
1498
|
-
"stripe:manage",
|
|
1499
|
-
"stripe:view"
|
|
1500
|
-
],
|
|
1501
|
-
"dependencies": [],
|
|
1502
|
-
"defaultSettings": {
|
|
1503
|
-
"stripeSecretKey": "",
|
|
1504
|
-
"stripeWebhookSecret": "",
|
|
1505
|
-
"stripePriceId": "",
|
|
1506
|
-
"successUrl": "/admin/dashboard",
|
|
1507
|
-
"cancelUrl": "/admin/dashboard"
|
|
1508
|
-
},
|
|
1509
|
-
"adminMenu": {
|
|
1510
|
-
"label": "Stripe",
|
|
1511
|
-
"icon": "credit-card",
|
|
1512
|
-
"path": "/admin/plugins/stripe",
|
|
1513
|
-
"order": 90
|
|
1514
|
-
}
|
|
1515
|
-
},
|
|
1516
|
-
"testimonials-plugin": {
|
|
1517
|
-
"id": "testimonials-plugin",
|
|
1518
|
-
"codeName": "testimonials-plugin",
|
|
1519
|
-
"displayName": "Testimonials",
|
|
1520
|
-
"description": "Customer testimonials and reviews management with display widgets and ratings",
|
|
1521
|
-
"version": "1.0.0-beta.1",
|
|
1522
|
-
"author": "SonicJS Team",
|
|
1523
|
-
"category": "content",
|
|
1524
|
-
"iconEmoji": "\u{1F4AC}",
|
|
1525
|
-
"is_core": false,
|
|
1526
|
-
"permissions": [
|
|
1527
|
-
"testimonials:manage"
|
|
1528
|
-
],
|
|
1529
|
-
"dependencies": [],
|
|
1530
|
-
"defaultSettings": {},
|
|
1531
|
-
"adminMenu": null
|
|
1532
|
-
},
|
|
1533
|
-
"tinymce-plugin": {
|
|
1534
|
-
"id": "tinymce-plugin",
|
|
1535
|
-
"codeName": "tinymce-plugin",
|
|
1536
|
-
"displayName": "TinyMCE Rich Text Editor",
|
|
1537
|
-
"description": "Powerful WYSIWYG rich text editor for content creation. Provides a full-featured editor with formatting, media embedding, and customizable toolbars for richtext fields.",
|
|
1538
|
-
"version": "1.0.0",
|
|
1539
|
-
"author": "SonicJS Team",
|
|
1540
|
-
"category": "editor",
|
|
1541
|
-
"iconEmoji": "\u{1F4DD}",
|
|
1542
|
-
"is_core": false,
|
|
1543
|
-
"permissions": [],
|
|
1544
|
-
"dependencies": [],
|
|
1545
|
-
"defaultSettings": {
|
|
1546
|
-
"apiKey": "no-api-key",
|
|
1547
|
-
"defaultHeight": 300,
|
|
1548
|
-
"defaultToolbar": "full",
|
|
1549
|
-
"skin": "oxide-dark"
|
|
1550
|
-
},
|
|
1551
|
-
"adminMenu": null
|
|
1552
|
-
},
|
|
1553
|
-
"turnstile": {
|
|
1554
|
-
"id": "turnstile",
|
|
1555
|
-
"codeName": "turnstile-plugin",
|
|
1556
|
-
"displayName": "Cloudflare Turnstile",
|
|
1557
|
-
"description": "CAPTCHA-free bot protection using Cloudflare Turnstile. Provides reusable verification for any form.",
|
|
1558
|
-
"version": "1.0.0",
|
|
1559
|
-
"author": "SonicJS",
|
|
1560
|
-
"category": "security",
|
|
1561
|
-
"iconEmoji": "\u{1F6E1}\uFE0F",
|
|
1562
|
-
"is_core": true,
|
|
1563
|
-
"permissions": [
|
|
1564
|
-
"settings:write",
|
|
1565
|
-
"admin:access"
|
|
1566
|
-
],
|
|
1567
|
-
"dependencies": [],
|
|
1568
|
-
"defaultSettings": {
|
|
1569
|
-
"siteKey": "",
|
|
1570
|
-
"secretKey": "",
|
|
1571
|
-
"theme": "auto",
|
|
1572
|
-
"size": "normal",
|
|
1573
|
-
"mode": "managed",
|
|
1574
|
-
"appearance": "always",
|
|
1575
|
-
"preClearanceEnabled": false,
|
|
1576
|
-
"preClearanceLevel": "managed",
|
|
1577
|
-
"enabled": false
|
|
1578
|
-
},
|
|
1579
|
-
"adminMenu": {
|
|
1580
|
-
"label": "Turnstile",
|
|
1581
|
-
"icon": "shield-check",
|
|
1582
|
-
"path": "/admin/plugins/turnstile/settings",
|
|
1583
|
-
"order": 100
|
|
1584
|
-
}
|
|
1585
|
-
},
|
|
1586
|
-
"user-profiles": {
|
|
1587
|
-
"id": "user-profiles",
|
|
1588
|
-
"codeName": "user-profiles",
|
|
1589
|
-
"displayName": "User Profiles",
|
|
1590
|
-
"description": "Configurable custom profile fields for users",
|
|
1591
|
-
"version": "1.0.0-beta.1",
|
|
1592
|
-
"author": "SonicJS Team",
|
|
1593
|
-
"category": "users",
|
|
1594
|
-
"iconEmoji": "\u{1F464}",
|
|
1595
|
-
"is_core": true,
|
|
1596
|
-
"permissions": [],
|
|
1597
|
-
"dependencies": [],
|
|
1598
|
-
"defaultSettings": {},
|
|
1599
|
-
"adminMenu": null
|
|
1600
|
-
},
|
|
1601
|
-
"workflow-plugin": {
|
|
1602
|
-
"id": "workflow-plugin",
|
|
1603
|
-
"codeName": "workflow-plugin",
|
|
1604
|
-
"displayName": "Workflow Engine",
|
|
1605
|
-
"description": "Content workflow and approval system with customizable states, transitions, and review processes",
|
|
1606
|
-
"version": "1.0.0-beta.1",
|
|
1607
|
-
"author": "SonicJS Team",
|
|
1608
|
-
"category": "content",
|
|
1609
|
-
"iconEmoji": "\u{1F504}",
|
|
1610
|
-
"is_core": false,
|
|
1611
|
-
"permissions": [
|
|
1612
|
-
"workflow:manage",
|
|
1613
|
-
"workflow:approve"
|
|
1614
|
-
],
|
|
1615
|
-
"dependencies": [],
|
|
1616
|
-
"defaultSettings": {
|
|
1617
|
-
"enableApprovalChains": true,
|
|
1618
|
-
"enableScheduling": true,
|
|
1619
|
-
"enableAutomation": true,
|
|
1620
|
-
"enableNotifications": true
|
|
1621
|
-
},
|
|
1622
|
-
"adminMenu": null
|
|
1623
|
-
}
|
|
1624
|
-
};
|
|
1625
|
-
var ALL_PLUGIN_IDS = Object.keys(PLUGIN_REGISTRY);
|
|
1626
|
-
ALL_PLUGIN_IDS.filter(
|
|
1627
|
-
(id) => PLUGIN_REGISTRY[id]?.adminMenu !== null
|
|
1628
|
-
);
|
|
1629
|
-
function findPluginByCodeName(codeName) {
|
|
1630
|
-
return Object.values(PLUGIN_REGISTRY).find((p) => p.codeName === codeName) || PLUGIN_REGISTRY[codeName];
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
// src/services/plugin-bootstrap.ts
|
|
1634
|
-
var BOOTSTRAP_PLUGIN_IDS = [
|
|
1635
|
-
"core-auth",
|
|
1636
|
-
"core-media",
|
|
1637
|
-
"database-tools",
|
|
1638
|
-
"seed-data",
|
|
1639
|
-
"core-cache",
|
|
1640
|
-
"workflow-plugin",
|
|
1641
|
-
"easy-mdx",
|
|
1642
|
-
"ai-search",
|
|
1643
|
-
"oauth-providers",
|
|
1644
|
-
"global-variables",
|
|
1645
|
-
"user-profiles",
|
|
1646
|
-
"stripe"
|
|
1647
|
-
];
|
|
1648
|
-
function registryToCorePlugin(entry) {
|
|
1649
|
-
return {
|
|
1650
|
-
id: entry.id,
|
|
1651
|
-
name: entry.codeName,
|
|
1652
|
-
display_name: entry.displayName,
|
|
1653
|
-
description: entry.description,
|
|
1654
|
-
version: entry.version,
|
|
1655
|
-
author: entry.author,
|
|
1656
|
-
category: entry.category,
|
|
1657
|
-
icon: entry.iconEmoji,
|
|
1658
|
-
permissions: entry.permissions,
|
|
1659
|
-
dependencies: entry.dependencies,
|
|
1660
|
-
settings: entry.defaultSettings
|
|
1661
|
-
};
|
|
1662
|
-
}
|
|
1663
|
-
var PluginBootstrapService = class {
|
|
1664
|
-
constructor(db) {
|
|
1665
|
-
this.db = db;
|
|
1666
|
-
this.pluginService = new PluginService(db);
|
|
1667
|
-
}
|
|
1668
|
-
pluginService;
|
|
1669
|
-
/**
|
|
1670
|
-
* Core plugins derived from the auto-generated plugin registry.
|
|
1671
|
-
* Only plugins listed in BOOTSTRAP_PLUGIN_IDS are included.
|
|
1672
|
-
*/
|
|
1673
|
-
CORE_PLUGINS = BOOTSTRAP_PLUGIN_IDS.filter((id) => PLUGIN_REGISTRY[id] !== void 0).map((id) => registryToCorePlugin(PLUGIN_REGISTRY[id]));
|
|
1674
|
-
/**
|
|
1675
|
-
* Bootstrap all core plugins - install them if they don't exist
|
|
1676
|
-
*/
|
|
1677
|
-
async bootstrapCorePlugins() {
|
|
1678
|
-
console.log("[PluginBootstrap] Starting core plugin bootstrap process...");
|
|
1679
|
-
try {
|
|
1680
|
-
for (const corePlugin of this.CORE_PLUGINS) {
|
|
1681
|
-
await this.ensurePluginInstalled(corePlugin);
|
|
1682
|
-
}
|
|
1683
|
-
console.log(
|
|
1684
|
-
"[PluginBootstrap] Core plugin bootstrap completed successfully"
|
|
1685
|
-
);
|
|
1686
|
-
} catch (error) {
|
|
1687
|
-
console.error("[PluginBootstrap] Error during plugin bootstrap:", error);
|
|
1688
|
-
throw error;
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
/**
|
|
1692
|
-
* Ensure a specific plugin is installed
|
|
1693
|
-
*/
|
|
1694
|
-
async ensurePluginInstalled(plugin) {
|
|
1695
|
-
try {
|
|
1696
|
-
const existingPlugin = await this.pluginService.getPlugin(plugin.id);
|
|
1697
|
-
if (existingPlugin) {
|
|
1698
|
-
console.log(
|
|
1699
|
-
`[PluginBootstrap] Plugin already installed: ${plugin.display_name} (status: ${existingPlugin.status})`
|
|
1700
|
-
);
|
|
1701
|
-
if (existingPlugin.version !== plugin.version) {
|
|
1702
|
-
console.log(
|
|
1703
|
-
`[PluginBootstrap] Updating plugin version: ${plugin.display_name} from ${existingPlugin.version} to ${plugin.version}`
|
|
1704
|
-
);
|
|
1705
|
-
await this.updatePlugin(plugin);
|
|
1706
|
-
}
|
|
1707
|
-
if (plugin.id === "core-auth" && existingPlugin.status !== "active") {
|
|
1708
|
-
console.log(
|
|
1709
|
-
`[PluginBootstrap] Core-auth plugin is inactive, activating it now...`
|
|
1710
|
-
);
|
|
1711
|
-
await this.pluginService.activatePlugin(plugin.id);
|
|
1712
|
-
}
|
|
1713
|
-
} else {
|
|
1714
|
-
console.log(
|
|
1715
|
-
`[PluginBootstrap] Installing plugin: ${plugin.display_name}`
|
|
1716
|
-
);
|
|
1717
|
-
await this.pluginService.installPlugin({
|
|
1718
|
-
...plugin,
|
|
1719
|
-
is_core: plugin.name.startsWith("core-")
|
|
1720
|
-
});
|
|
1721
|
-
console.log(
|
|
1722
|
-
`[PluginBootstrap] Activating newly installed plugin: ${plugin.display_name}`
|
|
1723
|
-
);
|
|
1724
|
-
await this.pluginService.activatePlugin(plugin.id);
|
|
1725
|
-
}
|
|
1726
|
-
} catch (error) {
|
|
1727
|
-
console.error(
|
|
1728
|
-
`[PluginBootstrap] Error ensuring plugin ${plugin.display_name}:`,
|
|
1729
|
-
error
|
|
1730
|
-
);
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
/**
|
|
1734
|
-
* Update an existing plugin
|
|
1735
|
-
*/
|
|
1736
|
-
async updatePlugin(plugin) {
|
|
1737
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1738
|
-
const stmt = this.db.prepare(`
|
|
1739
|
-
UPDATE plugins
|
|
1740
|
-
SET
|
|
1741
|
-
version = ?,
|
|
1742
|
-
description = ?,
|
|
1743
|
-
permissions = ?,
|
|
1744
|
-
settings = ?,
|
|
1745
|
-
last_updated = ?
|
|
1746
|
-
WHERE id = ?
|
|
1747
|
-
`);
|
|
1748
|
-
await stmt.bind(
|
|
1749
|
-
plugin.version,
|
|
1750
|
-
plugin.description,
|
|
1751
|
-
JSON.stringify(plugin.permissions),
|
|
1752
|
-
JSON.stringify(plugin.settings || {}),
|
|
1753
|
-
now,
|
|
1754
|
-
plugin.id
|
|
1755
|
-
).run();
|
|
1756
|
-
}
|
|
1757
|
-
/**
|
|
1758
|
-
* Check if bootstrap is needed (first run detection)
|
|
1759
|
-
*/
|
|
1760
|
-
async isBootstrapNeeded() {
|
|
1761
|
-
try {
|
|
1762
|
-
for (const corePlugin of this.CORE_PLUGINS.filter(
|
|
1763
|
-
(p) => p.name.startsWith("core-")
|
|
1764
|
-
)) {
|
|
1765
|
-
const exists = await this.pluginService.getPlugin(corePlugin.id);
|
|
1766
|
-
if (!exists) {
|
|
1767
|
-
return true;
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
return false;
|
|
1771
|
-
} catch (error) {
|
|
1772
|
-
console.error(
|
|
1773
|
-
"[PluginBootstrap] Error checking bootstrap status:",
|
|
1774
|
-
error
|
|
1775
|
-
);
|
|
1776
|
-
return true;
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
};
|
|
1780
|
-
|
|
1781
|
-
export { PLUGIN_REGISTRY, PluginBootstrapService, PluginService, backfillFormSubmissions, cleanupRemovedCollections, createContentFromSubmission, deriveCollectionSchemaFromFormio, deriveSubmissionTitle, findPluginByCodeName, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, mapFormStatusToContentStatus, registerCollections, syncAllFormCollections, syncCollection, syncCollections, syncFormCollection, validateCollectionConfig };
|
|
1782
|
-
//# sourceMappingURL=chunk-EW5NOBVU.js.map
|
|
1783
|
-
//# sourceMappingURL=chunk-EW5NOBVU.js.map
|