@sonicjs-cms/core 1.0.0-alpha.1 → 1.0.0-alpha.3
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/dist/chunk-24PWAFUT.cjs +776 -0
- package/dist/chunk-24PWAFUT.cjs.map +1 -0
- package/dist/chunk-4URGXJP7.js +3 -0
- package/dist/{chunk-PZ5AY32C.js.map → chunk-4URGXJP7.js.map} +1 -1
- package/dist/chunk-ALTMI5Y2.cjs +4 -0
- package/dist/{chunk-Q7SFCCGT.cjs.map → chunk-ALTMI5Y2.cjs.map} +1 -1
- package/dist/chunk-BOLQHE4J.cjs +11 -0
- package/dist/chunk-BOLQHE4J.cjs.map +1 -0
- package/dist/chunk-BRC3F4CG.cjs +792 -0
- package/dist/chunk-BRC3F4CG.cjs.map +1 -0
- package/dist/chunk-CXZDAR6S.js +2360 -0
- package/dist/chunk-CXZDAR6S.js.map +1 -0
- package/dist/chunk-EMMSS5I5.cjs +37 -0
- package/dist/chunk-EMMSS5I5.cjs.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-HD7R6T6I.js +9 -0
- package/dist/chunk-HD7R6T6I.js.map +1 -0
- package/dist/chunk-KRJMGD4E.js +783 -0
- package/dist/chunk-KRJMGD4E.js.map +1 -0
- package/dist/chunk-L3NXO7Y4.cjs +3093 -0
- package/dist/chunk-L3NXO7Y4.cjs.map +1 -0
- package/dist/chunk-NRSL6BQI.js +3086 -0
- package/dist/chunk-NRSL6BQI.js.map +1 -0
- package/dist/chunk-PTQZ5FEI.js +755 -0
- package/dist/chunk-PTQZ5FEI.js.map +1 -0
- package/dist/chunk-WJ7QYVR2.cjs +2416 -0
- package/dist/chunk-WJ7QYVR2.cjs.map +1 -0
- package/dist/collection-config-FLlGtsh9.d.cts +107 -0
- package/dist/collection-config-FLlGtsh9.d.ts +107 -0
- package/dist/index-BlsY5XNH.d.ts +8333 -0
- package/dist/index-D45jaIlr.d.cts +8333 -0
- package/dist/index.cjs +365 -630
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -7962
- package/dist/index.d.ts +16 -7962
- package/dist/index.js +10 -592
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +84 -4
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +203 -5
- package/dist/middleware.d.ts +203 -5
- package/dist/middleware.js +3 -6
- package/dist/middleware.js.map +1 -1
- package/dist/plugin-UzmDImQc.d.cts +357 -0
- package/dist/plugin-UzmDImQc.d.ts +357 -0
- package/dist/plugins.cjs +28 -4
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.d.cts +326 -4
- package/dist/plugins.d.ts +326 -4
- package/dist/plugins.js +3 -6
- package/dist/plugins.js.map +1 -1
- package/dist/routes.cjs +7 -4
- package/dist/routes.cjs.map +1 -1
- package/dist/routes.d.cts +12 -3
- package/dist/routes.d.ts +12 -3
- package/dist/routes.js +2 -6
- package/dist/routes.js.map +1 -1
- package/dist/services.cjs +68 -4
- package/dist/services.cjs.map +1 -1
- package/dist/services.d.cts +5 -8
- package/dist/services.d.ts +5 -8
- package/dist/services.js +3 -6
- package/dist/services.js.map +1 -1
- package/dist/templates.cjs +35 -4
- package/dist/templates.cjs.map +1 -1
- package/dist/templates.d.cts +137 -5
- package/dist/templates.d.ts +137 -5
- package/dist/templates.js +2 -6
- package/dist/templates.js.map +1 -1
- package/dist/types.cjs +1 -1
- package/dist/types.d.cts +6 -462
- package/dist/types.d.ts +6 -462
- package/dist/types.js +1 -1
- package/dist/utils.cjs +1 -1
- package/dist/utils.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-PZ5AY32C.js +0 -9
- package/dist/chunk-Q7SFCCGT.cjs +0 -11
|
@@ -0,0 +1,2416 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkEMMSS5I5_cjs = require('./chunk-EMMSS5I5.cjs');
|
|
4
|
+
var sqliteCore = require('drizzle-orm/sqlite-core');
|
|
5
|
+
var v4 = require('zod/v4');
|
|
6
|
+
var drizzleOrm = require('drizzle-orm');
|
|
7
|
+
var d1 = require('drizzle-orm/d1');
|
|
8
|
+
|
|
9
|
+
// src/services/collection-loader.ts
|
|
10
|
+
async function loadCollectionConfigs() {
|
|
11
|
+
const collections2 = [];
|
|
12
|
+
try {
|
|
13
|
+
const modules = undefined?.("../collections/*.collection.ts", { eager: true }) || {};
|
|
14
|
+
for (const [path, module] of Object.entries(modules)) {
|
|
15
|
+
try {
|
|
16
|
+
const configModule = module;
|
|
17
|
+
if (!configModule.default) {
|
|
18
|
+
console.warn(`Collection file ${path} does not export a default config`);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const config = configModule.default;
|
|
22
|
+
if (!config.name || !config.displayName || !config.schema) {
|
|
23
|
+
console.error(`Invalid collection config in ${path}: missing required fields`);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const normalizedConfig = {
|
|
27
|
+
...config,
|
|
28
|
+
managed: config.managed !== void 0 ? config.managed : true,
|
|
29
|
+
isActive: config.isActive !== void 0 ? config.isActive : true
|
|
30
|
+
};
|
|
31
|
+
collections2.push(normalizedConfig);
|
|
32
|
+
console.log(`\u2713 Loaded collection config: ${config.name}`);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(`Error loading collection from ${path}:`, error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
console.log(`Loaded ${collections2.length} collection configuration(s)`);
|
|
38
|
+
return collections2;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("Error loading collection configurations:", error);
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function loadCollectionConfig(name) {
|
|
45
|
+
try {
|
|
46
|
+
console.warn("loadCollectionConfig requires implementation in consuming application");
|
|
47
|
+
return null;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`Error loading collection ${name}:`, error);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function getAvailableCollectionNames() {
|
|
54
|
+
try {
|
|
55
|
+
const modules = undefined?.("../collections/*.collection.ts") || {};
|
|
56
|
+
const names = [];
|
|
57
|
+
for (const path of Object.keys(modules)) {
|
|
58
|
+
const match = path.match(/\/([^/]+)\.collection\.ts$/);
|
|
59
|
+
if (match && match[1]) {
|
|
60
|
+
names.push(match[1]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return names;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Error getting collection names:", error);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function validateCollectionConfig(config) {
|
|
70
|
+
const errors = [];
|
|
71
|
+
if (!config.name) {
|
|
72
|
+
errors.push("Collection name is required");
|
|
73
|
+
} else if (!/^[a-z0-9_]+$/.test(config.name)) {
|
|
74
|
+
errors.push("Collection name must contain only lowercase letters, numbers, and underscores");
|
|
75
|
+
}
|
|
76
|
+
if (!config.displayName) {
|
|
77
|
+
errors.push("Display name is required");
|
|
78
|
+
}
|
|
79
|
+
if (!config.schema) {
|
|
80
|
+
errors.push("Schema is required");
|
|
81
|
+
} else {
|
|
82
|
+
if (config.schema.type !== "object") {
|
|
83
|
+
errors.push('Schema type must be "object"');
|
|
84
|
+
}
|
|
85
|
+
if (!config.schema.properties || typeof config.schema.properties !== "object") {
|
|
86
|
+
errors.push("Schema must have properties");
|
|
87
|
+
}
|
|
88
|
+
for (const [fieldName, fieldConfig] of Object.entries(config.schema.properties || {})) {
|
|
89
|
+
if (!fieldConfig.type) {
|
|
90
|
+
errors.push(`Field "${fieldName}" is missing type`);
|
|
91
|
+
}
|
|
92
|
+
if (fieldConfig.type === "reference" && !fieldConfig.collection) {
|
|
93
|
+
errors.push(`Reference field "${fieldName}" is missing collection property`);
|
|
94
|
+
}
|
|
95
|
+
if (["select", "multiselect", "radio"].includes(fieldConfig.type) && !fieldConfig.enum) {
|
|
96
|
+
errors.push(`Select field "${fieldName}" is missing enum options`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
valid: errors.length === 0,
|
|
102
|
+
errors
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/services/collection-sync.ts
|
|
107
|
+
async function syncCollections(db) {
|
|
108
|
+
console.log("\u{1F504} Starting collection sync...");
|
|
109
|
+
const results = [];
|
|
110
|
+
const configs = await loadCollectionConfigs();
|
|
111
|
+
if (configs.length === 0) {
|
|
112
|
+
console.log("\u26A0\uFE0F No collection configurations found");
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
for (const config of configs) {
|
|
116
|
+
const result = await syncCollection(db, config);
|
|
117
|
+
results.push(result);
|
|
118
|
+
}
|
|
119
|
+
const created = results.filter((r) => r.status === "created").length;
|
|
120
|
+
const updated = results.filter((r) => r.status === "updated").length;
|
|
121
|
+
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
122
|
+
const errors = results.filter((r) => r.status === "error").length;
|
|
123
|
+
console.log(`\u2705 Collection sync complete: ${created} created, ${updated} updated, ${unchanged} unchanged, ${errors} errors`);
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
async function syncCollection(db, config) {
|
|
127
|
+
try {
|
|
128
|
+
const validation = validateCollectionConfig(config);
|
|
129
|
+
if (!validation.valid) {
|
|
130
|
+
return {
|
|
131
|
+
name: config.name,
|
|
132
|
+
status: "error",
|
|
133
|
+
error: `Validation failed: ${validation.errors.join(", ")}`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const existingStmt = db.prepare("SELECT * FROM collections WHERE name = ?");
|
|
137
|
+
const existing = await existingStmt.bind(config.name).first();
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const collectionId = existing?.id || `col-${config.name}-${crypto.randomUUID().slice(0, 8)}`;
|
|
140
|
+
const schemaJson = JSON.stringify(config.schema);
|
|
141
|
+
const isActive = config.isActive !== false ? 1 : 0;
|
|
142
|
+
const managed = config.managed !== false ? 1 : 0;
|
|
143
|
+
if (!existing) {
|
|
144
|
+
const insertStmt = db.prepare(`
|
|
145
|
+
INSERT INTO collections (id, name, display_name, description, schema, is_active, managed, created_at, updated_at)
|
|
146
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
147
|
+
`);
|
|
148
|
+
await insertStmt.bind(
|
|
149
|
+
collectionId,
|
|
150
|
+
config.name,
|
|
151
|
+
config.displayName,
|
|
152
|
+
config.description || null,
|
|
153
|
+
schemaJson,
|
|
154
|
+
isActive,
|
|
155
|
+
managed,
|
|
156
|
+
now,
|
|
157
|
+
now
|
|
158
|
+
).run();
|
|
159
|
+
console.log(` \u2713 Created collection: ${config.name}`);
|
|
160
|
+
return {
|
|
161
|
+
name: config.name,
|
|
162
|
+
status: "created",
|
|
163
|
+
message: `Created collection "${config.displayName}"`
|
|
164
|
+
};
|
|
165
|
+
} else {
|
|
166
|
+
const existingSchema = existing.schema ? JSON.stringify(existing.schema) : "{}";
|
|
167
|
+
const existingDisplayName = existing.display_name;
|
|
168
|
+
const existingDescription = existing.description;
|
|
169
|
+
const existingIsActive = existing.is_active;
|
|
170
|
+
const existingManaged = existing.managed;
|
|
171
|
+
const needsUpdate = schemaJson !== existingSchema || config.displayName !== existingDisplayName || (config.description || null) !== existingDescription || isActive !== existingIsActive || managed !== existingManaged;
|
|
172
|
+
if (!needsUpdate) {
|
|
173
|
+
return {
|
|
174
|
+
name: config.name,
|
|
175
|
+
status: "unchanged",
|
|
176
|
+
message: `Collection "${config.displayName}" is up to date`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const updateStmt = db.prepare(`
|
|
180
|
+
UPDATE collections
|
|
181
|
+
SET display_name = ?, description = ?, schema = ?, is_active = ?, managed = ?, updated_at = ?
|
|
182
|
+
WHERE name = ?
|
|
183
|
+
`);
|
|
184
|
+
await updateStmt.bind(
|
|
185
|
+
config.displayName,
|
|
186
|
+
config.description || null,
|
|
187
|
+
schemaJson,
|
|
188
|
+
isActive,
|
|
189
|
+
managed,
|
|
190
|
+
now,
|
|
191
|
+
config.name
|
|
192
|
+
).run();
|
|
193
|
+
console.log(` \u2713 Updated collection: ${config.name}`);
|
|
194
|
+
return {
|
|
195
|
+
name: config.name,
|
|
196
|
+
status: "updated",
|
|
197
|
+
message: `Updated collection "${config.displayName}"`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error(` \u2717 Error syncing collection ${config.name}:`, error);
|
|
202
|
+
return {
|
|
203
|
+
name: config.name,
|
|
204
|
+
status: "error",
|
|
205
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function isCollectionManaged(db, collectionName) {
|
|
210
|
+
try {
|
|
211
|
+
const stmt = db.prepare("SELECT managed FROM collections WHERE name = ?");
|
|
212
|
+
const result = await stmt.bind(collectionName).first();
|
|
213
|
+
return result?.managed === 1;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(`Error checking if collection is managed:`, error);
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function getManagedCollections(db) {
|
|
220
|
+
try {
|
|
221
|
+
const stmt = db.prepare("SELECT name FROM collections WHERE managed = 1");
|
|
222
|
+
const { results } = await stmt.all();
|
|
223
|
+
return (results || []).map((row) => row.name);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error("Error getting managed collections:", error);
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function cleanupRemovedCollections(db) {
|
|
230
|
+
try {
|
|
231
|
+
const configs = await loadCollectionConfigs();
|
|
232
|
+
const configNames = new Set(configs.map((c) => c.name));
|
|
233
|
+
const managedCollections = await getManagedCollections(db);
|
|
234
|
+
const removed = [];
|
|
235
|
+
for (const managedName of managedCollections) {
|
|
236
|
+
if (!configNames.has(managedName)) {
|
|
237
|
+
const updateStmt = db.prepare(`
|
|
238
|
+
UPDATE collections
|
|
239
|
+
SET is_active = 0, updated_at = ?
|
|
240
|
+
WHERE name = ? AND managed = 1
|
|
241
|
+
`);
|
|
242
|
+
await updateStmt.bind(Date.now(), managedName).run();
|
|
243
|
+
removed.push(managedName);
|
|
244
|
+
console.log(` \u26A0\uFE0F Deactivated removed collection: ${managedName}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return removed;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error("Error cleaning up removed collections:", error);
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function fullCollectionSync(db) {
|
|
254
|
+
const results = await syncCollections(db);
|
|
255
|
+
const removed = await cleanupRemovedCollections(db);
|
|
256
|
+
return { results, removed };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/services/migrations.ts
|
|
260
|
+
var MigrationService = class {
|
|
261
|
+
constructor(db) {
|
|
262
|
+
this.db = db;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Initialize the migrations tracking table
|
|
266
|
+
*/
|
|
267
|
+
async initializeMigrationsTable() {
|
|
268
|
+
const createTableQuery = `
|
|
269
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
270
|
+
id TEXT PRIMARY KEY,
|
|
271
|
+
name TEXT NOT NULL,
|
|
272
|
+
filename TEXT NOT NULL,
|
|
273
|
+
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
274
|
+
checksum TEXT
|
|
275
|
+
)
|
|
276
|
+
`;
|
|
277
|
+
await this.db.prepare(createTableQuery).run();
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get all available migrations from the migrations directory
|
|
281
|
+
*/
|
|
282
|
+
async getAvailableMigrations() {
|
|
283
|
+
const migrations = [];
|
|
284
|
+
const migrationFiles = [
|
|
285
|
+
{ id: "001", name: "Initial Schema", filename: "001_initial_schema.sql", description: "Initial database schema with users, content, collections, and media tables" },
|
|
286
|
+
{ id: "002", name: "FAQ Plugin", filename: "002_faq_plugin.sql", description: "FAQ plugin tables and initial data" },
|
|
287
|
+
{ id: "003", name: "Stage 5 Enhancements", filename: "003_stage5_enhancements.sql", description: "Enhanced content management and media handling" },
|
|
288
|
+
{ id: "004", name: "User Management", filename: "004_stage6_user_management.sql", description: "Advanced user management with roles and permissions" },
|
|
289
|
+
{ id: "005", name: "Workflow & Automation", filename: "005_stage7_workflow_automation.sql", description: "Workflow states, automation rules, and scheduled content" },
|
|
290
|
+
{ id: "006", name: "Plugin System", filename: "006_plugin_system.sql", description: "Plugin registration and configuration system" }
|
|
291
|
+
];
|
|
292
|
+
const appliedResult = await this.db.prepare(
|
|
293
|
+
"SELECT id, name, filename, applied_at FROM migrations ORDER BY applied_at ASC"
|
|
294
|
+
).all();
|
|
295
|
+
const appliedMigrations = new Map(
|
|
296
|
+
appliedResult.results?.map((row) => [row.id, row]) || []
|
|
297
|
+
);
|
|
298
|
+
await this.autoDetectAppliedMigrations(appliedMigrations);
|
|
299
|
+
for (const file of migrationFiles) {
|
|
300
|
+
const applied = appliedMigrations.has(file.id);
|
|
301
|
+
const appliedData = appliedMigrations.get(file.id);
|
|
302
|
+
migrations.push({
|
|
303
|
+
id: file.id,
|
|
304
|
+
name: file.name,
|
|
305
|
+
filename: file.filename,
|
|
306
|
+
description: file.description,
|
|
307
|
+
applied,
|
|
308
|
+
appliedAt: applied ? appliedData?.applied_at : void 0,
|
|
309
|
+
size: await this.getMigrationFileSize(file.filename)
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return migrations;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Auto-detect applied migrations by checking if their tables exist
|
|
316
|
+
*/
|
|
317
|
+
async autoDetectAppliedMigrations(appliedMigrations) {
|
|
318
|
+
if (!appliedMigrations.has("001")) {
|
|
319
|
+
const hasBasicTables = await this.checkTablesExist(["users", "content", "collections", "media"]);
|
|
320
|
+
if (hasBasicTables) {
|
|
321
|
+
appliedMigrations.set("001", {
|
|
322
|
+
id: "001",
|
|
323
|
+
applied_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
324
|
+
name: "Initial Schema",
|
|
325
|
+
filename: "001_initial_schema.sql"
|
|
326
|
+
});
|
|
327
|
+
await this.markMigrationApplied("001", "Initial Schema", "001_initial_schema.sql");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (!appliedMigrations.has("002")) {
|
|
331
|
+
const hasFaqTables = await this.checkTablesExist(["faqs", "faq_categories"]);
|
|
332
|
+
if (hasFaqTables) {
|
|
333
|
+
appliedMigrations.set("002", {
|
|
334
|
+
id: "002",
|
|
335
|
+
applied_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
336
|
+
name: "FAQ Plugin",
|
|
337
|
+
filename: "002_faq_plugin.sql"
|
|
338
|
+
});
|
|
339
|
+
await this.markMigrationApplied("002", "FAQ Plugin", "002_faq_plugin.sql");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (!appliedMigrations.has("003")) {
|
|
343
|
+
const hasEnhancedTables = await this.checkTablesExist(["content_versions", "email_themes", "email_templates"]);
|
|
344
|
+
if (hasEnhancedTables) {
|
|
345
|
+
appliedMigrations.set("003", {
|
|
346
|
+
id: "003",
|
|
347
|
+
applied_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
348
|
+
name: "Stage 5 Enhancements",
|
|
349
|
+
filename: "003_stage5_enhancements.sql"
|
|
350
|
+
});
|
|
351
|
+
await this.markMigrationApplied("003", "Stage 5 Enhancements", "003_stage5_enhancements.sql");
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!appliedMigrations.has("004")) {
|
|
355
|
+
const hasUserTables = await this.checkTablesExist(["api_tokens", "workflow_history"]);
|
|
356
|
+
if (hasUserTables) {
|
|
357
|
+
appliedMigrations.set("004", {
|
|
358
|
+
id: "004",
|
|
359
|
+
applied_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
360
|
+
name: "User Management",
|
|
361
|
+
filename: "004_stage6_user_management.sql"
|
|
362
|
+
});
|
|
363
|
+
await this.markMigrationApplied("004", "User Management", "004_stage6_user_management.sql");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (!appliedMigrations.has("006")) {
|
|
367
|
+
const hasPluginTables = await this.checkTablesExist(["plugins", "plugin_hooks"]);
|
|
368
|
+
if (hasPluginTables) {
|
|
369
|
+
appliedMigrations.set("006", {
|
|
370
|
+
id: "006",
|
|
371
|
+
applied_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
372
|
+
name: "Plugin System",
|
|
373
|
+
filename: "006_plugin_system.sql"
|
|
374
|
+
});
|
|
375
|
+
await this.markMigrationApplied("006", "Plugin System", "006_plugin_system.sql");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Check if specific tables exist in the database
|
|
381
|
+
*/
|
|
382
|
+
async checkTablesExist(tableNames) {
|
|
383
|
+
try {
|
|
384
|
+
for (const tableName of tableNames) {
|
|
385
|
+
const result = await this.db.prepare(
|
|
386
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`
|
|
387
|
+
).bind(tableName).first();
|
|
388
|
+
if (!result) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get migration status summary
|
|
399
|
+
*/
|
|
400
|
+
async getMigrationStatus() {
|
|
401
|
+
await this.initializeMigrationsTable();
|
|
402
|
+
const migrations = await this.getAvailableMigrations();
|
|
403
|
+
const appliedMigrations = migrations.filter((m) => m.applied);
|
|
404
|
+
const pendingMigrations = migrations.filter((m) => !m.applied);
|
|
405
|
+
const lastApplied = appliedMigrations.length > 0 ? appliedMigrations[appliedMigrations.length - 1]?.appliedAt : void 0;
|
|
406
|
+
return {
|
|
407
|
+
totalMigrations: migrations.length,
|
|
408
|
+
appliedMigrations: appliedMigrations.length,
|
|
409
|
+
pendingMigrations: pendingMigrations.length,
|
|
410
|
+
lastApplied,
|
|
411
|
+
migrations
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Mark a migration as applied
|
|
416
|
+
*/
|
|
417
|
+
async markMigrationApplied(migrationId, name, filename) {
|
|
418
|
+
await this.initializeMigrationsTable();
|
|
419
|
+
await this.db.prepare(
|
|
420
|
+
"INSERT OR REPLACE INTO migrations (id, name, filename, applied_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)"
|
|
421
|
+
).bind(migrationId, name, filename).run();
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Check if a specific migration has been applied
|
|
425
|
+
*/
|
|
426
|
+
async isMigrationApplied(migrationId) {
|
|
427
|
+
await this.initializeMigrationsTable();
|
|
428
|
+
const result = await this.db.prepare(
|
|
429
|
+
"SELECT COUNT(*) as count FROM migrations WHERE id = ?"
|
|
430
|
+
).bind(migrationId).first();
|
|
431
|
+
return result?.count > 0;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Get the last applied migration
|
|
435
|
+
*/
|
|
436
|
+
async getLastAppliedMigration() {
|
|
437
|
+
await this.initializeMigrationsTable();
|
|
438
|
+
const result = await this.db.prepare(
|
|
439
|
+
"SELECT id, name, filename, applied_at FROM migrations ORDER BY applied_at DESC LIMIT 1"
|
|
440
|
+
).first();
|
|
441
|
+
if (!result) return null;
|
|
442
|
+
return {
|
|
443
|
+
id: result.id,
|
|
444
|
+
name: result.name,
|
|
445
|
+
filename: result.filename,
|
|
446
|
+
applied: true,
|
|
447
|
+
appliedAt: result.applied_at
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Get migration file size (simulated)
|
|
452
|
+
*/
|
|
453
|
+
async getMigrationFileSize(filename) {
|
|
454
|
+
const sizesMap = {
|
|
455
|
+
"001_initial_schema.sql": 15420,
|
|
456
|
+
"002_faq_plugin.sql": 2340,
|
|
457
|
+
"003_stage5_enhancements.sql": 8920,
|
|
458
|
+
"004_stage6_user_management.sql": 12680,
|
|
459
|
+
"005_stage7_workflow_automation.sql": 18750,
|
|
460
|
+
"006_plugin_system.sql": 5430
|
|
461
|
+
};
|
|
462
|
+
return sizesMap[filename] || 1e3;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Run pending migrations
|
|
466
|
+
*/
|
|
467
|
+
async runPendingMigrations() {
|
|
468
|
+
const status = await this.getMigrationStatus();
|
|
469
|
+
const pendingMigrations = status.migrations.filter((m) => !m.applied);
|
|
470
|
+
if (pendingMigrations.length === 0) {
|
|
471
|
+
return {
|
|
472
|
+
success: true,
|
|
473
|
+
message: "All migrations are up to date",
|
|
474
|
+
applied: []
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const applied = [];
|
|
478
|
+
for (const migration of pendingMigrations) {
|
|
479
|
+
try {
|
|
480
|
+
await this.applyMigration(migration);
|
|
481
|
+
await this.markMigrationApplied(migration.id, migration.name, migration.filename);
|
|
482
|
+
applied.push(migration.id);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error(`Failed to apply migration ${migration.id}:`, error);
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
success: true,
|
|
490
|
+
message: `Applied ${applied.length} migration(s)`,
|
|
491
|
+
applied
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Apply a specific migration
|
|
496
|
+
*/
|
|
497
|
+
async applyMigration(migration) {
|
|
498
|
+
console.log(`Applying migration ${migration.id}: ${migration.name}`);
|
|
499
|
+
const migrationSQL = await this.getMigrationSQL(migration.id);
|
|
500
|
+
if (migrationSQL === null) {
|
|
501
|
+
throw new Error(`Migration SQL not found for ${migration.id}`);
|
|
502
|
+
}
|
|
503
|
+
if (migrationSQL === "") {
|
|
504
|
+
console.log(`Skipping migration ${migration.id} (empty/obsolete)`);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const statements = this.splitSQLStatements(migrationSQL);
|
|
508
|
+
for (const statement of statements) {
|
|
509
|
+
if (statement.trim()) {
|
|
510
|
+
try {
|
|
511
|
+
await this.db.prepare(statement).run();
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.error(`Error executing statement: ${statement}`, error);
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Split SQL into statements, handling CREATE TRIGGER properly
|
|
521
|
+
*/
|
|
522
|
+
splitSQLStatements(sql) {
|
|
523
|
+
const statements = [];
|
|
524
|
+
let current = "";
|
|
525
|
+
let inTrigger = false;
|
|
526
|
+
const lines = sql.split("\n");
|
|
527
|
+
for (const line of lines) {
|
|
528
|
+
const trimmed = line.trim();
|
|
529
|
+
if (trimmed.startsWith("--") || trimmed.length === 0) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (trimmed.toUpperCase().includes("CREATE TRIGGER")) {
|
|
533
|
+
inTrigger = true;
|
|
534
|
+
}
|
|
535
|
+
current += line + "\n";
|
|
536
|
+
if (inTrigger && trimmed.toUpperCase() === "END;") {
|
|
537
|
+
statements.push(current.trim());
|
|
538
|
+
current = "";
|
|
539
|
+
inTrigger = false;
|
|
540
|
+
} else if (!inTrigger && trimmed.endsWith(";")) {
|
|
541
|
+
statements.push(current.trim());
|
|
542
|
+
current = "";
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (current.trim()) {
|
|
546
|
+
statements.push(current.trim());
|
|
547
|
+
}
|
|
548
|
+
return statements.filter((s) => s.length > 0);
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Get migration SQL by ID
|
|
552
|
+
*/
|
|
553
|
+
async getMigrationSQL(migrationId) {
|
|
554
|
+
switch (migrationId) {
|
|
555
|
+
case "001":
|
|
556
|
+
return `
|
|
557
|
+
-- Initial schema for SonicJS AI
|
|
558
|
+
-- Create users table for authentication
|
|
559
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
560
|
+
id TEXT PRIMARY KEY,
|
|
561
|
+
email TEXT NOT NULL UNIQUE,
|
|
562
|
+
username TEXT NOT NULL UNIQUE,
|
|
563
|
+
first_name TEXT NOT NULL,
|
|
564
|
+
last_name TEXT NOT NULL,
|
|
565
|
+
password_hash TEXT,
|
|
566
|
+
role TEXT NOT NULL DEFAULT 'viewer',
|
|
567
|
+
avatar TEXT,
|
|
568
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
569
|
+
last_login_at INTEGER,
|
|
570
|
+
created_at INTEGER NOT NULL,
|
|
571
|
+
updated_at INTEGER NOT NULL
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
-- Create collections table for content schema definitions
|
|
575
|
+
CREATE TABLE IF NOT EXISTS collections (
|
|
576
|
+
id TEXT PRIMARY KEY,
|
|
577
|
+
name TEXT NOT NULL UNIQUE,
|
|
578
|
+
display_name TEXT NOT NULL,
|
|
579
|
+
description TEXT,
|
|
580
|
+
schema TEXT NOT NULL,
|
|
581
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
582
|
+
created_at INTEGER NOT NULL,
|
|
583
|
+
updated_at INTEGER NOT NULL
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
-- Create content table for actual content data
|
|
587
|
+
CREATE TABLE IF NOT EXISTS content (
|
|
588
|
+
id TEXT PRIMARY KEY,
|
|
589
|
+
collection_id TEXT NOT NULL REFERENCES collections(id),
|
|
590
|
+
slug TEXT NOT NULL,
|
|
591
|
+
title TEXT NOT NULL,
|
|
592
|
+
data TEXT NOT NULL,
|
|
593
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
594
|
+
published_at INTEGER,
|
|
595
|
+
author_id TEXT NOT NULL REFERENCES users(id),
|
|
596
|
+
created_by TEXT NOT NULL REFERENCES users(id),
|
|
597
|
+
created_at INTEGER NOT NULL,
|
|
598
|
+
updated_at INTEGER NOT NULL
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
-- Create content_versions table for versioning
|
|
602
|
+
CREATE TABLE IF NOT EXISTS content_versions (
|
|
603
|
+
id TEXT PRIMARY KEY,
|
|
604
|
+
content_id TEXT NOT NULL REFERENCES content(id),
|
|
605
|
+
version INTEGER NOT NULL,
|
|
606
|
+
data TEXT NOT NULL,
|
|
607
|
+
author_id TEXT NOT NULL REFERENCES users(id),
|
|
608
|
+
created_at INTEGER NOT NULL
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
-- Create media/files table with comprehensive R2 integration
|
|
612
|
+
CREATE TABLE IF NOT EXISTS media (
|
|
613
|
+
id TEXT PRIMARY KEY,
|
|
614
|
+
filename TEXT NOT NULL,
|
|
615
|
+
original_name TEXT NOT NULL,
|
|
616
|
+
mime_type TEXT NOT NULL,
|
|
617
|
+
size INTEGER NOT NULL,
|
|
618
|
+
width INTEGER,
|
|
619
|
+
height INTEGER,
|
|
620
|
+
folder TEXT NOT NULL DEFAULT 'uploads',
|
|
621
|
+
r2_key TEXT NOT NULL,
|
|
622
|
+
public_url TEXT NOT NULL,
|
|
623
|
+
thumbnail_url TEXT,
|
|
624
|
+
alt TEXT,
|
|
625
|
+
caption TEXT,
|
|
626
|
+
tags TEXT,
|
|
627
|
+
uploaded_by TEXT NOT NULL REFERENCES users(id),
|
|
628
|
+
uploaded_at INTEGER NOT NULL,
|
|
629
|
+
updated_at INTEGER,
|
|
630
|
+
published_at INTEGER,
|
|
631
|
+
scheduled_at INTEGER,
|
|
632
|
+
archived_at INTEGER,
|
|
633
|
+
deleted_at INTEGER
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
-- Create API tokens table for programmatic access
|
|
637
|
+
CREATE TABLE IF NOT EXISTS api_tokens (
|
|
638
|
+
id TEXT PRIMARY KEY,
|
|
639
|
+
name TEXT NOT NULL,
|
|
640
|
+
token TEXT NOT NULL UNIQUE,
|
|
641
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
642
|
+
permissions TEXT NOT NULL,
|
|
643
|
+
expires_at INTEGER,
|
|
644
|
+
last_used_at INTEGER,
|
|
645
|
+
created_at INTEGER NOT NULL
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
-- Create workflow history table for content workflow tracking
|
|
649
|
+
CREATE TABLE IF NOT EXISTS workflow_history (
|
|
650
|
+
id TEXT PRIMARY KEY,
|
|
651
|
+
content_id TEXT NOT NULL REFERENCES content(id),
|
|
652
|
+
action TEXT NOT NULL,
|
|
653
|
+
from_status TEXT NOT NULL,
|
|
654
|
+
to_status TEXT NOT NULL,
|
|
655
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
656
|
+
comment TEXT,
|
|
657
|
+
created_at INTEGER NOT NULL
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
-- Create indexes for better performance
|
|
661
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
662
|
+
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
663
|
+
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
|
664
|
+
|
|
665
|
+
CREATE INDEX IF NOT EXISTS idx_collections_name ON collections(name);
|
|
666
|
+
CREATE INDEX IF NOT EXISTS idx_collections_active ON collections(is_active);
|
|
667
|
+
|
|
668
|
+
CREATE INDEX IF NOT EXISTS idx_content_collection ON content(collection_id);
|
|
669
|
+
CREATE INDEX IF NOT EXISTS idx_content_author ON content(author_id);
|
|
670
|
+
CREATE INDEX IF NOT EXISTS idx_content_status ON content(status);
|
|
671
|
+
CREATE INDEX IF NOT EXISTS idx_content_published ON content(published_at);
|
|
672
|
+
CREATE INDEX IF NOT EXISTS idx_content_slug ON content(slug);
|
|
673
|
+
|
|
674
|
+
CREATE INDEX IF NOT EXISTS idx_content_versions_content ON content_versions(content_id);
|
|
675
|
+
CREATE INDEX IF NOT EXISTS idx_content_versions_version ON content_versions(version);
|
|
676
|
+
|
|
677
|
+
CREATE INDEX IF NOT EXISTS idx_media_folder ON media(folder);
|
|
678
|
+
CREATE INDEX IF NOT EXISTS idx_media_type ON media(mime_type);
|
|
679
|
+
CREATE INDEX IF NOT EXISTS idx_media_uploaded_by ON media(uploaded_by);
|
|
680
|
+
CREATE INDEX IF NOT EXISTS idx_media_uploaded_at ON media(uploaded_at);
|
|
681
|
+
CREATE INDEX IF NOT EXISTS idx_media_deleted ON media(deleted_at);
|
|
682
|
+
|
|
683
|
+
CREATE INDEX IF NOT EXISTS idx_api_tokens_user ON api_tokens(user_id);
|
|
684
|
+
CREATE INDEX IF NOT EXISTS idx_api_tokens_token ON api_tokens(token);
|
|
685
|
+
|
|
686
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_history_content ON workflow_history(content_id);
|
|
687
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_history_user ON workflow_history(user_id);
|
|
688
|
+
|
|
689
|
+
-- Insert default admin user (password: admin123)
|
|
690
|
+
INSERT OR IGNORE INTO users (
|
|
691
|
+
id, email, username, first_name, last_name, password_hash,
|
|
692
|
+
role, is_active, created_at, updated_at
|
|
693
|
+
) VALUES (
|
|
694
|
+
'admin-user-id',
|
|
695
|
+
'admin@sonicjs.com',
|
|
696
|
+
'admin',
|
|
697
|
+
'Admin',
|
|
698
|
+
'User',
|
|
699
|
+
'd1c379e871838f44e21d5a55841349e50636f06df139bfef11870eec74c381db',
|
|
700
|
+
'admin',
|
|
701
|
+
1,
|
|
702
|
+
strftime('%s', 'now') * 1000,
|
|
703
|
+
strftime('%s', 'now') * 1000
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
-- Insert sample collections
|
|
707
|
+
INSERT OR IGNORE INTO collections (
|
|
708
|
+
id, name, display_name, description, schema,
|
|
709
|
+
is_active, created_at, updated_at
|
|
710
|
+
) VALUES (
|
|
711
|
+
'blog-posts-collection',
|
|
712
|
+
'blog_posts',
|
|
713
|
+
'Blog Posts',
|
|
714
|
+
'Blog post content collection',
|
|
715
|
+
'{"type":"object","properties":{"title":{"type":"string","title":"Title","required":true},"content":{"type":"string","title":"Content","format":"richtext"},"excerpt":{"type":"string","title":"Excerpt"},"featured_image":{"type":"string","title":"Featured Image","format":"media"},"tags":{"type":"array","title":"Tags","items":{"type":"string"}},"status":{"type":"string","title":"Status","enum":["draft","published","archived"],"default":"draft"}},"required":["title"]}',
|
|
716
|
+
1,
|
|
717
|
+
strftime('%s', 'now') * 1000,
|
|
718
|
+
strftime('%s', 'now') * 1000
|
|
719
|
+
),
|
|
720
|
+
(
|
|
721
|
+
'pages-collection',
|
|
722
|
+
'pages',
|
|
723
|
+
'Pages',
|
|
724
|
+
'Static page content collection',
|
|
725
|
+
'{"type":"object","properties":{"title":{"type":"string","title":"Title","required":true},"content":{"type":"string","title":"Content","format":"richtext"},"slug":{"type":"string","title":"Slug"},"meta_description":{"type":"string","title":"Meta Description"},"featured_image":{"type":"string","title":"Featured Image","format":"media"}},"required":["title"]}',
|
|
726
|
+
1,
|
|
727
|
+
strftime('%s', 'now') * 1000,
|
|
728
|
+
strftime('%s', 'now') * 1000
|
|
729
|
+
),
|
|
730
|
+
(
|
|
731
|
+
'news-collection',
|
|
732
|
+
'news',
|
|
733
|
+
'News',
|
|
734
|
+
'News article content collection',
|
|
735
|
+
'{"type":"object","properties":{"title":{"type":"string","title":"Title","required":true},"content":{"type":"string","title":"Content","format":"richtext"},"publish_date":{"type":"string","title":"Publish Date","format":"date"},"author":{"type":"string","title":"Author"},"category":{"type":"string","title":"Category","enum":["technology","business","general"]}},"required":["title"]}',
|
|
736
|
+
1,
|
|
737
|
+
strftime('%s', 'now') * 1000,
|
|
738
|
+
strftime('%s', 'now') * 1000
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
-- Insert sample content
|
|
742
|
+
INSERT OR IGNORE INTO content (
|
|
743
|
+
id, collection_id, slug, title, data, status,
|
|
744
|
+
author_id, created_by, created_at, updated_at
|
|
745
|
+
) VALUES (
|
|
746
|
+
'welcome-blog-post',
|
|
747
|
+
'blog-posts-collection',
|
|
748
|
+
'welcome-to-sonicjs-ai',
|
|
749
|
+
'Welcome to SonicJS AI',
|
|
750
|
+
'{"title":"Welcome to SonicJS AI","content":"<h1>Welcome to SonicJS AI</h1><p>This is your first blog post created with SonicJS AI, a modern headless CMS built on Cloudflare Workers.</p><h2>Features</h2><ul><li>Cloudflare-native architecture</li><li>TypeScript-first development</li><li>Hono.js framework</li><li>D1 database</li><li>R2 media storage</li><li>Edge computing</li></ul><p>Get started by exploring the admin interface and creating your own content!</p>","excerpt":"Welcome to SonicJS AI, a modern headless CMS built on Cloudflare Workers with TypeScript and Hono.js.","status":"published","tags":["welcome","cms","cloudflare"]}',
|
|
751
|
+
'published',
|
|
752
|
+
'admin-user-id',
|
|
753
|
+
'admin-user-id',
|
|
754
|
+
strftime('%s', 'now') * 1000,
|
|
755
|
+
strftime('%s', 'now') * 1000
|
|
756
|
+
);
|
|
757
|
+
`;
|
|
758
|
+
case "002":
|
|
759
|
+
return `
|
|
760
|
+
CREATE TABLE IF NOT EXISTS faqs (
|
|
761
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
762
|
+
question TEXT NOT NULL,
|
|
763
|
+
answer TEXT NOT NULL,
|
|
764
|
+
category TEXT,
|
|
765
|
+
tags TEXT,
|
|
766
|
+
isPublished INTEGER NOT NULL DEFAULT 1,
|
|
767
|
+
sortOrder INTEGER NOT NULL DEFAULT 0,
|
|
768
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
|
769
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
CREATE INDEX IF NOT EXISTS idx_faqs_category ON faqs(category);
|
|
773
|
+
CREATE INDEX IF NOT EXISTS idx_faqs_published ON faqs(isPublished);
|
|
774
|
+
CREATE INDEX IF NOT EXISTS idx_faqs_sort_order ON faqs(sortOrder);
|
|
775
|
+
|
|
776
|
+
CREATE TRIGGER IF NOT EXISTS faqs_updated_at
|
|
777
|
+
AFTER UPDATE ON faqs
|
|
778
|
+
BEGIN
|
|
779
|
+
UPDATE faqs SET updated_at = strftime('%s', 'now') WHERE id = NEW.id;
|
|
780
|
+
END;
|
|
781
|
+
`;
|
|
782
|
+
case "006":
|
|
783
|
+
return `
|
|
784
|
+
-- Plugin System Tables
|
|
785
|
+
CREATE TABLE IF NOT EXISTS plugins (
|
|
786
|
+
id TEXT PRIMARY KEY,
|
|
787
|
+
name TEXT NOT NULL UNIQUE,
|
|
788
|
+
display_name TEXT NOT NULL,
|
|
789
|
+
description TEXT,
|
|
790
|
+
version TEXT NOT NULL,
|
|
791
|
+
author TEXT NOT NULL,
|
|
792
|
+
category TEXT NOT NULL,
|
|
793
|
+
icon TEXT,
|
|
794
|
+
status TEXT DEFAULT 'inactive' CHECK (status IN ('active', 'inactive', 'error')),
|
|
795
|
+
is_core BOOLEAN DEFAULT FALSE,
|
|
796
|
+
settings JSON,
|
|
797
|
+
permissions JSON,
|
|
798
|
+
dependencies JSON,
|
|
799
|
+
download_count INTEGER DEFAULT 0,
|
|
800
|
+
rating REAL DEFAULT 0,
|
|
801
|
+
installed_at INTEGER NOT NULL,
|
|
802
|
+
activated_at INTEGER,
|
|
803
|
+
last_updated INTEGER NOT NULL,
|
|
804
|
+
error_message TEXT,
|
|
805
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
806
|
+
updated_at INTEGER DEFAULT (unixepoch())
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
CREATE TABLE IF NOT EXISTS plugin_hooks (
|
|
810
|
+
id TEXT PRIMARY KEY,
|
|
811
|
+
plugin_id TEXT NOT NULL,
|
|
812
|
+
hook_name TEXT NOT NULL,
|
|
813
|
+
handler_name TEXT NOT NULL,
|
|
814
|
+
priority INTEGER DEFAULT 10,
|
|
815
|
+
is_active BOOLEAN DEFAULT TRUE,
|
|
816
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
817
|
+
FOREIGN KEY (plugin_id) REFERENCES plugins(id) ON DELETE CASCADE,
|
|
818
|
+
UNIQUE(plugin_id, hook_name, handler_name)
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
CREATE TABLE IF NOT EXISTS plugin_routes (
|
|
822
|
+
id TEXT PRIMARY KEY,
|
|
823
|
+
plugin_id TEXT NOT NULL,
|
|
824
|
+
path TEXT NOT NULL,
|
|
825
|
+
method TEXT NOT NULL,
|
|
826
|
+
handler_name TEXT NOT NULL,
|
|
827
|
+
middleware JSON,
|
|
828
|
+
is_active BOOLEAN DEFAULT TRUE,
|
|
829
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
830
|
+
FOREIGN KEY (plugin_id) REFERENCES plugins(id) ON DELETE CASCADE,
|
|
831
|
+
UNIQUE(plugin_id, path, method)
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
CREATE TABLE IF NOT EXISTS plugin_assets (
|
|
835
|
+
id TEXT PRIMARY KEY,
|
|
836
|
+
plugin_id TEXT NOT NULL,
|
|
837
|
+
asset_type TEXT NOT NULL CHECK (asset_type IN ('css', 'js', 'image', 'font')),
|
|
838
|
+
asset_path TEXT NOT NULL,
|
|
839
|
+
load_order INTEGER DEFAULT 100,
|
|
840
|
+
load_location TEXT DEFAULT 'footer' CHECK (load_location IN ('header', 'footer')),
|
|
841
|
+
is_active BOOLEAN DEFAULT TRUE,
|
|
842
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
843
|
+
FOREIGN KEY (plugin_id) REFERENCES plugins(id) ON DELETE CASCADE
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
CREATE TABLE IF NOT EXISTS plugin_activity_log (
|
|
847
|
+
id TEXT PRIMARY KEY,
|
|
848
|
+
plugin_id TEXT NOT NULL,
|
|
849
|
+
action TEXT NOT NULL,
|
|
850
|
+
user_id TEXT,
|
|
851
|
+
details JSON,
|
|
852
|
+
timestamp INTEGER DEFAULT (unixepoch()),
|
|
853
|
+
FOREIGN KEY (plugin_id) REFERENCES plugins(id) ON DELETE CASCADE
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
-- Create indexes
|
|
857
|
+
CREATE INDEX IF NOT EXISTS idx_plugins_status ON plugins(status);
|
|
858
|
+
CREATE INDEX IF NOT EXISTS idx_plugins_category ON plugins(category);
|
|
859
|
+
CREATE INDEX IF NOT EXISTS idx_plugin_hooks_plugin ON plugin_hooks(plugin_id);
|
|
860
|
+
CREATE INDEX IF NOT EXISTS idx_plugin_routes_plugin ON plugin_routes(plugin_id);
|
|
861
|
+
CREATE INDEX IF NOT EXISTS idx_plugin_assets_plugin ON plugin_assets(plugin_id);
|
|
862
|
+
CREATE INDEX IF NOT EXISTS idx_plugin_activity_plugin ON plugin_activity_log(plugin_id);
|
|
863
|
+
CREATE INDEX IF NOT EXISTS idx_plugin_activity_timestamp ON plugin_activity_log(timestamp);
|
|
864
|
+
|
|
865
|
+
-- Insert core plugins
|
|
866
|
+
INSERT OR IGNORE INTO plugins (
|
|
867
|
+
id, name, display_name, description, version, author, category, icon,
|
|
868
|
+
status, is_core, permissions, installed_at, last_updated
|
|
869
|
+
) VALUES
|
|
870
|
+
(
|
|
871
|
+
'core-auth',
|
|
872
|
+
'core-auth',
|
|
873
|
+
'Authentication System',
|
|
874
|
+
'Core authentication and user management system',
|
|
875
|
+
'1.0.0',
|
|
876
|
+
'SonicJS Team',
|
|
877
|
+
'security',
|
|
878
|
+
'\u{1F510}',
|
|
879
|
+
'active',
|
|
880
|
+
TRUE,
|
|
881
|
+
'["manage:users", "manage:roles", "manage:permissions"]',
|
|
882
|
+
unixepoch(),
|
|
883
|
+
unixepoch()
|
|
884
|
+
),
|
|
885
|
+
(
|
|
886
|
+
'core-media',
|
|
887
|
+
'core-media',
|
|
888
|
+
'Media Manager',
|
|
889
|
+
'Core media upload and management system',
|
|
890
|
+
'1.0.0',
|
|
891
|
+
'SonicJS Team',
|
|
892
|
+
'media',
|
|
893
|
+
'\u{1F4F8}',
|
|
894
|
+
'active',
|
|
895
|
+
TRUE,
|
|
896
|
+
'["manage:media", "upload:files"]',
|
|
897
|
+
unixepoch(),
|
|
898
|
+
unixepoch()
|
|
899
|
+
),
|
|
900
|
+
(
|
|
901
|
+
'core-workflow',
|
|
902
|
+
'core-workflow',
|
|
903
|
+
'Workflow Engine',
|
|
904
|
+
'Content workflow and approval system',
|
|
905
|
+
'1.0.0',
|
|
906
|
+
'SonicJS Team',
|
|
907
|
+
'content',
|
|
908
|
+
'\u{1F504}',
|
|
909
|
+
'active',
|
|
910
|
+
TRUE,
|
|
911
|
+
'["manage:workflows", "approve:content"]',
|
|
912
|
+
unixepoch(),
|
|
913
|
+
unixepoch()
|
|
914
|
+
),
|
|
915
|
+
(
|
|
916
|
+
'cache',
|
|
917
|
+
'cache',
|
|
918
|
+
'Cache System',
|
|
919
|
+
'Three-tiered caching system with memory, KV, and database layers',
|
|
920
|
+
'1.0.0',
|
|
921
|
+
'SonicJS Team',
|
|
922
|
+
'performance',
|
|
923
|
+
'\u26A1',
|
|
924
|
+
'active',
|
|
925
|
+
TRUE,
|
|
926
|
+
'["manage:cache","view:stats"]',
|
|
927
|
+
unixepoch(),
|
|
928
|
+
unixepoch()
|
|
929
|
+
),
|
|
930
|
+
(
|
|
931
|
+
'design',
|
|
932
|
+
'design-plugin',
|
|
933
|
+
'Design System',
|
|
934
|
+
'Design system management including themes, components, and UI customization. Provides a visual interface for managing design tokens, typography, colors, and component library.',
|
|
935
|
+
'1.0.0',
|
|
936
|
+
'SonicJS',
|
|
937
|
+
'ui',
|
|
938
|
+
'\u{1F3A8}',
|
|
939
|
+
'active',
|
|
940
|
+
TRUE,
|
|
941
|
+
'["design.view", "design.edit"]',
|
|
942
|
+
unixepoch(),
|
|
943
|
+
unixepoch()
|
|
944
|
+
);
|
|
945
|
+
`;
|
|
946
|
+
case "003":
|
|
947
|
+
case "004":
|
|
948
|
+
case "005":
|
|
949
|
+
case "007":
|
|
950
|
+
case "008":
|
|
951
|
+
case "009":
|
|
952
|
+
case "011":
|
|
953
|
+
case "012":
|
|
954
|
+
case "013":
|
|
955
|
+
return "";
|
|
956
|
+
default:
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Validate database schema (placeholder)
|
|
962
|
+
*/
|
|
963
|
+
async validateSchema() {
|
|
964
|
+
const issues = [];
|
|
965
|
+
const requiredTables = [
|
|
966
|
+
"users",
|
|
967
|
+
"content",
|
|
968
|
+
"collections",
|
|
969
|
+
"media",
|
|
970
|
+
"sessions"
|
|
971
|
+
];
|
|
972
|
+
for (const table of requiredTables) {
|
|
973
|
+
try {
|
|
974
|
+
await this.db.prepare(`SELECT COUNT(*) FROM ${table} LIMIT 1`).first();
|
|
975
|
+
} catch (error) {
|
|
976
|
+
issues.push(`Missing table: ${table}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return {
|
|
980
|
+
valid: issues.length === 0,
|
|
981
|
+
issues
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
// src/db/schema.ts
|
|
987
|
+
var schema_exports = {};
|
|
988
|
+
chunkEMMSS5I5_cjs.__export(schema_exports, {
|
|
989
|
+
apiTokens: () => apiTokens,
|
|
990
|
+
collections: () => collections,
|
|
991
|
+
content: () => content,
|
|
992
|
+
contentVersions: () => contentVersions,
|
|
993
|
+
insertCollectionSchema: () => insertCollectionSchema,
|
|
994
|
+
insertContentSchema: () => insertContentSchema,
|
|
995
|
+
insertLogConfigSchema: () => insertLogConfigSchema,
|
|
996
|
+
insertMediaSchema: () => insertMediaSchema,
|
|
997
|
+
insertPluginActivityLogSchema: () => insertPluginActivityLogSchema,
|
|
998
|
+
insertPluginAssetSchema: () => insertPluginAssetSchema,
|
|
999
|
+
insertPluginHookSchema: () => insertPluginHookSchema,
|
|
1000
|
+
insertPluginRouteSchema: () => insertPluginRouteSchema,
|
|
1001
|
+
insertPluginSchema: () => insertPluginSchema,
|
|
1002
|
+
insertSystemLogSchema: () => insertSystemLogSchema,
|
|
1003
|
+
insertUserSchema: () => insertUserSchema,
|
|
1004
|
+
insertWorkflowHistorySchema: () => insertWorkflowHistorySchema,
|
|
1005
|
+
logConfig: () => logConfig,
|
|
1006
|
+
media: () => media,
|
|
1007
|
+
pluginActivityLog: () => pluginActivityLog,
|
|
1008
|
+
pluginAssets: () => pluginAssets,
|
|
1009
|
+
pluginHooks: () => pluginHooks,
|
|
1010
|
+
pluginRoutes: () => pluginRoutes,
|
|
1011
|
+
plugins: () => plugins,
|
|
1012
|
+
selectCollectionSchema: () => selectCollectionSchema,
|
|
1013
|
+
selectContentSchema: () => selectContentSchema,
|
|
1014
|
+
selectLogConfigSchema: () => selectLogConfigSchema,
|
|
1015
|
+
selectMediaSchema: () => selectMediaSchema,
|
|
1016
|
+
selectPluginActivityLogSchema: () => selectPluginActivityLogSchema,
|
|
1017
|
+
selectPluginAssetSchema: () => selectPluginAssetSchema,
|
|
1018
|
+
selectPluginHookSchema: () => selectPluginHookSchema,
|
|
1019
|
+
selectPluginRouteSchema: () => selectPluginRouteSchema,
|
|
1020
|
+
selectPluginSchema: () => selectPluginSchema,
|
|
1021
|
+
selectSystemLogSchema: () => selectSystemLogSchema,
|
|
1022
|
+
selectUserSchema: () => selectUserSchema,
|
|
1023
|
+
selectWorkflowHistorySchema: () => selectWorkflowHistorySchema,
|
|
1024
|
+
systemLogs: () => systemLogs,
|
|
1025
|
+
users: () => users,
|
|
1026
|
+
workflowHistory: () => workflowHistory
|
|
1027
|
+
});
|
|
1028
|
+
var CONSTANTS = {
|
|
1029
|
+
INT8_MIN: -128,
|
|
1030
|
+
INT8_MAX: 127,
|
|
1031
|
+
INT8_UNSIGNED_MAX: 255,
|
|
1032
|
+
INT16_MIN: -32768,
|
|
1033
|
+
INT16_MAX: 32767,
|
|
1034
|
+
INT16_UNSIGNED_MAX: 65535,
|
|
1035
|
+
INT24_MIN: -8388608,
|
|
1036
|
+
INT24_MAX: 8388607,
|
|
1037
|
+
INT24_UNSIGNED_MAX: 16777215,
|
|
1038
|
+
INT32_MIN: -2147483648,
|
|
1039
|
+
INT32_MAX: 2147483647,
|
|
1040
|
+
INT32_UNSIGNED_MAX: 4294967295,
|
|
1041
|
+
INT48_MIN: -140737488355328,
|
|
1042
|
+
INT48_MAX: 140737488355327,
|
|
1043
|
+
INT48_UNSIGNED_MAX: 281474976710655,
|
|
1044
|
+
INT64_MIN: -9223372036854775808n,
|
|
1045
|
+
INT64_MAX: 9223372036854775807n,
|
|
1046
|
+
INT64_UNSIGNED_MAX: 18446744073709551615n
|
|
1047
|
+
};
|
|
1048
|
+
function isColumnType(column, columnTypes) {
|
|
1049
|
+
return columnTypes.includes(column.columnType);
|
|
1050
|
+
}
|
|
1051
|
+
function isWithEnum(column) {
|
|
1052
|
+
return "enumValues" in column && Array.isArray(column.enumValues) && column.enumValues.length > 0;
|
|
1053
|
+
}
|
|
1054
|
+
var isPgEnum = isWithEnum;
|
|
1055
|
+
var literalSchema = v4.z.union([v4.z.string(), v4.z.number(), v4.z.boolean(), v4.z.null()]);
|
|
1056
|
+
var jsonSchema = v4.z.union([
|
|
1057
|
+
literalSchema,
|
|
1058
|
+
v4.z.record(v4.z.string(), v4.z.any()),
|
|
1059
|
+
v4.z.array(v4.z.any())
|
|
1060
|
+
]);
|
|
1061
|
+
var bufferSchema = v4.z.custom((v) => v instanceof Buffer);
|
|
1062
|
+
function columnToSchema(column, factory) {
|
|
1063
|
+
const z$1 = v4.z;
|
|
1064
|
+
const coerce = {};
|
|
1065
|
+
let schema;
|
|
1066
|
+
if (isWithEnum(column)) {
|
|
1067
|
+
schema = column.enumValues.length ? z$1.enum(column.enumValues) : z$1.string();
|
|
1068
|
+
}
|
|
1069
|
+
if (!schema) {
|
|
1070
|
+
if (isColumnType(column, ["PgGeometry", "PgPointTuple"])) {
|
|
1071
|
+
schema = z$1.tuple([z$1.number(), z$1.number()]);
|
|
1072
|
+
} else if (isColumnType(column, ["PgGeometryObject", "PgPointObject"])) {
|
|
1073
|
+
schema = z$1.object({ x: z$1.number(), y: z$1.number() });
|
|
1074
|
+
} else if (isColumnType(column, ["PgHalfVector", "PgVector"])) {
|
|
1075
|
+
schema = z$1.array(z$1.number());
|
|
1076
|
+
schema = column.dimensions ? schema.length(column.dimensions) : schema;
|
|
1077
|
+
} else if (isColumnType(column, ["PgLine"])) {
|
|
1078
|
+
schema = z$1.tuple([z$1.number(), z$1.number(), z$1.number()]);
|
|
1079
|
+
} else if (isColumnType(column, ["PgLineABC"])) {
|
|
1080
|
+
schema = z$1.object({
|
|
1081
|
+
a: z$1.number(),
|
|
1082
|
+
b: z$1.number(),
|
|
1083
|
+
c: z$1.number()
|
|
1084
|
+
});
|
|
1085
|
+
} else if (isColumnType(column, ["PgArray"])) {
|
|
1086
|
+
schema = z$1.array(columnToSchema(column.baseColumn));
|
|
1087
|
+
schema = column.size ? schema.length(column.size) : schema;
|
|
1088
|
+
} else if (column.dataType === "array") {
|
|
1089
|
+
schema = z$1.array(z$1.any());
|
|
1090
|
+
} else if (column.dataType === "number") {
|
|
1091
|
+
schema = numberColumnToSchema(column, z$1, coerce);
|
|
1092
|
+
} else if (column.dataType === "bigint") {
|
|
1093
|
+
schema = bigintColumnToSchema(column, z$1, coerce);
|
|
1094
|
+
} else if (column.dataType === "boolean") {
|
|
1095
|
+
schema = coerce === true || coerce.boolean ? z$1.coerce.boolean() : z$1.boolean();
|
|
1096
|
+
} else if (column.dataType === "date") {
|
|
1097
|
+
schema = coerce === true || coerce.date ? z$1.coerce.date() : z$1.date();
|
|
1098
|
+
} else if (column.dataType === "string") {
|
|
1099
|
+
schema = stringColumnToSchema(column, z$1, coerce);
|
|
1100
|
+
} else if (column.dataType === "json") {
|
|
1101
|
+
schema = jsonSchema;
|
|
1102
|
+
} else if (column.dataType === "custom") {
|
|
1103
|
+
schema = z$1.any();
|
|
1104
|
+
} else if (column.dataType === "buffer") {
|
|
1105
|
+
schema = bufferSchema;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (!schema) {
|
|
1109
|
+
schema = z$1.any();
|
|
1110
|
+
}
|
|
1111
|
+
return schema;
|
|
1112
|
+
}
|
|
1113
|
+
function numberColumnToSchema(column, z2, coerce) {
|
|
1114
|
+
let unsigned = column.getSQLType().includes("unsigned");
|
|
1115
|
+
let min;
|
|
1116
|
+
let max;
|
|
1117
|
+
let integer2 = false;
|
|
1118
|
+
if (isColumnType(column, ["MySqlTinyInt", "SingleStoreTinyInt"])) {
|
|
1119
|
+
min = unsigned ? 0 : CONSTANTS.INT8_MIN;
|
|
1120
|
+
max = unsigned ? CONSTANTS.INT8_UNSIGNED_MAX : CONSTANTS.INT8_MAX;
|
|
1121
|
+
integer2 = true;
|
|
1122
|
+
} else if (isColumnType(column, [
|
|
1123
|
+
"PgSmallInt",
|
|
1124
|
+
"PgSmallSerial",
|
|
1125
|
+
"MySqlSmallInt",
|
|
1126
|
+
"SingleStoreSmallInt"
|
|
1127
|
+
])) {
|
|
1128
|
+
min = unsigned ? 0 : CONSTANTS.INT16_MIN;
|
|
1129
|
+
max = unsigned ? CONSTANTS.INT16_UNSIGNED_MAX : CONSTANTS.INT16_MAX;
|
|
1130
|
+
integer2 = true;
|
|
1131
|
+
} else if (isColumnType(column, [
|
|
1132
|
+
"PgReal",
|
|
1133
|
+
"MySqlFloat",
|
|
1134
|
+
"MySqlMediumInt",
|
|
1135
|
+
"SingleStoreMediumInt",
|
|
1136
|
+
"SingleStoreFloat"
|
|
1137
|
+
])) {
|
|
1138
|
+
min = unsigned ? 0 : CONSTANTS.INT24_MIN;
|
|
1139
|
+
max = unsigned ? CONSTANTS.INT24_UNSIGNED_MAX : CONSTANTS.INT24_MAX;
|
|
1140
|
+
integer2 = isColumnType(column, ["MySqlMediumInt", "SingleStoreMediumInt"]);
|
|
1141
|
+
} else if (isColumnType(column, [
|
|
1142
|
+
"PgInteger",
|
|
1143
|
+
"PgSerial",
|
|
1144
|
+
"MySqlInt",
|
|
1145
|
+
"SingleStoreInt"
|
|
1146
|
+
])) {
|
|
1147
|
+
min = unsigned ? 0 : CONSTANTS.INT32_MIN;
|
|
1148
|
+
max = unsigned ? CONSTANTS.INT32_UNSIGNED_MAX : CONSTANTS.INT32_MAX;
|
|
1149
|
+
integer2 = true;
|
|
1150
|
+
} else if (isColumnType(column, [
|
|
1151
|
+
"PgDoublePrecision",
|
|
1152
|
+
"MySqlReal",
|
|
1153
|
+
"MySqlDouble",
|
|
1154
|
+
"SingleStoreReal",
|
|
1155
|
+
"SingleStoreDouble",
|
|
1156
|
+
"SQLiteReal"
|
|
1157
|
+
])) {
|
|
1158
|
+
min = unsigned ? 0 : CONSTANTS.INT48_MIN;
|
|
1159
|
+
max = unsigned ? CONSTANTS.INT48_UNSIGNED_MAX : CONSTANTS.INT48_MAX;
|
|
1160
|
+
} else if (isColumnType(column, [
|
|
1161
|
+
"PgBigInt53",
|
|
1162
|
+
"PgBigSerial53",
|
|
1163
|
+
"MySqlBigInt53",
|
|
1164
|
+
"MySqlSerial",
|
|
1165
|
+
"SingleStoreBigInt53",
|
|
1166
|
+
"SingleStoreSerial",
|
|
1167
|
+
"SQLiteInteger"
|
|
1168
|
+
])) {
|
|
1169
|
+
unsigned = unsigned || isColumnType(column, ["MySqlSerial", "SingleStoreSerial"]);
|
|
1170
|
+
min = unsigned ? 0 : Number.MIN_SAFE_INTEGER;
|
|
1171
|
+
max = Number.MAX_SAFE_INTEGER;
|
|
1172
|
+
integer2 = true;
|
|
1173
|
+
} else if (isColumnType(column, ["MySqlYear", "SingleStoreYear"])) {
|
|
1174
|
+
min = 1901;
|
|
1175
|
+
max = 2155;
|
|
1176
|
+
integer2 = true;
|
|
1177
|
+
} else {
|
|
1178
|
+
min = Number.MIN_SAFE_INTEGER;
|
|
1179
|
+
max = Number.MAX_SAFE_INTEGER;
|
|
1180
|
+
}
|
|
1181
|
+
let schema = coerce === true || coerce?.number ? integer2 ? z2.coerce.number() : z2.coerce.number().int() : integer2 ? z2.int() : z2.number();
|
|
1182
|
+
schema = schema.gte(min).lte(max);
|
|
1183
|
+
return schema;
|
|
1184
|
+
}
|
|
1185
|
+
function bigintColumnToSchema(column, z2, coerce) {
|
|
1186
|
+
const unsigned = column.getSQLType().includes("unsigned");
|
|
1187
|
+
const min = unsigned ? 0n : CONSTANTS.INT64_MIN;
|
|
1188
|
+
const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX;
|
|
1189
|
+
const schema = coerce === true || coerce?.bigint ? z2.coerce.bigint() : z2.bigint();
|
|
1190
|
+
return schema.gte(min).lte(max);
|
|
1191
|
+
}
|
|
1192
|
+
function stringColumnToSchema(column, z2, coerce) {
|
|
1193
|
+
if (isColumnType(column, ["PgUUID"])) {
|
|
1194
|
+
return z2.uuid();
|
|
1195
|
+
}
|
|
1196
|
+
let max;
|
|
1197
|
+
let regex;
|
|
1198
|
+
let fixed = false;
|
|
1199
|
+
if (isColumnType(column, ["PgVarchar", "SQLiteText"])) {
|
|
1200
|
+
max = column.length;
|
|
1201
|
+
} else if (isColumnType(column, ["MySqlVarChar", "SingleStoreVarChar"])) {
|
|
1202
|
+
max = column.length ?? CONSTANTS.INT16_UNSIGNED_MAX;
|
|
1203
|
+
} else if (isColumnType(column, ["MySqlText", "SingleStoreText"])) {
|
|
1204
|
+
if (column.textType === "longtext") {
|
|
1205
|
+
max = CONSTANTS.INT32_UNSIGNED_MAX;
|
|
1206
|
+
} else if (column.textType === "mediumtext") {
|
|
1207
|
+
max = CONSTANTS.INT24_UNSIGNED_MAX;
|
|
1208
|
+
} else if (column.textType === "text") {
|
|
1209
|
+
max = CONSTANTS.INT16_UNSIGNED_MAX;
|
|
1210
|
+
} else {
|
|
1211
|
+
max = CONSTANTS.INT8_UNSIGNED_MAX;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (isColumnType(column, [
|
|
1215
|
+
"PgChar",
|
|
1216
|
+
"MySqlChar",
|
|
1217
|
+
"SingleStoreChar"
|
|
1218
|
+
])) {
|
|
1219
|
+
max = column.length;
|
|
1220
|
+
fixed = true;
|
|
1221
|
+
}
|
|
1222
|
+
if (isColumnType(column, ["PgBinaryVector"])) {
|
|
1223
|
+
regex = /^[01]+$/;
|
|
1224
|
+
max = column.dimensions;
|
|
1225
|
+
}
|
|
1226
|
+
let schema = coerce === true || coerce?.string ? z2.coerce.string() : z2.string();
|
|
1227
|
+
schema = regex ? schema.regex(regex) : schema;
|
|
1228
|
+
return max && fixed ? schema.length(max) : max ? schema.max(max) : schema;
|
|
1229
|
+
}
|
|
1230
|
+
function getColumns(tableLike) {
|
|
1231
|
+
return drizzleOrm.isTable(tableLike) ? drizzleOrm.getTableColumns(tableLike) : drizzleOrm.getViewSelectedFields(tableLike);
|
|
1232
|
+
}
|
|
1233
|
+
function handleColumns(columns, refinements, conditions, factory) {
|
|
1234
|
+
const columnSchemas = {};
|
|
1235
|
+
for (const [key, selected] of Object.entries(columns)) {
|
|
1236
|
+
if (!drizzleOrm.is(selected, drizzleOrm.Column) && !drizzleOrm.is(selected, drizzleOrm.SQL) && !drizzleOrm.is(selected, drizzleOrm.SQL.Aliased) && typeof selected === "object") {
|
|
1237
|
+
const columns2 = drizzleOrm.isTable(selected) || drizzleOrm.isView(selected) ? getColumns(selected) : selected;
|
|
1238
|
+
columnSchemas[key] = handleColumns(columns2, refinements[key] ?? {}, conditions);
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
const refinement = refinements[key];
|
|
1242
|
+
if (refinement !== void 0 && typeof refinement !== "function") {
|
|
1243
|
+
columnSchemas[key] = refinement;
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
const column = drizzleOrm.is(selected, drizzleOrm.Column) ? selected : void 0;
|
|
1247
|
+
const schema = column ? columnToSchema(column) : v4.z.any();
|
|
1248
|
+
const refined = typeof refinement === "function" ? refinement(schema) : schema;
|
|
1249
|
+
if (conditions.never(column)) {
|
|
1250
|
+
continue;
|
|
1251
|
+
} else {
|
|
1252
|
+
columnSchemas[key] = refined;
|
|
1253
|
+
}
|
|
1254
|
+
if (column) {
|
|
1255
|
+
if (conditions.nullable(column)) {
|
|
1256
|
+
columnSchemas[key] = columnSchemas[key].nullable();
|
|
1257
|
+
}
|
|
1258
|
+
if (conditions.optional(column)) {
|
|
1259
|
+
columnSchemas[key] = columnSchemas[key].optional();
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return v4.z.object(columnSchemas);
|
|
1264
|
+
}
|
|
1265
|
+
function handleEnum(enum_, factory) {
|
|
1266
|
+
const zod = v4.z;
|
|
1267
|
+
return zod.enum(enum_.enumValues);
|
|
1268
|
+
}
|
|
1269
|
+
var selectConditions = {
|
|
1270
|
+
never: () => false,
|
|
1271
|
+
optional: () => false,
|
|
1272
|
+
nullable: (column) => !column.notNull
|
|
1273
|
+
};
|
|
1274
|
+
var insertConditions = {
|
|
1275
|
+
never: (column) => column?.generated?.type === "always" || column?.generatedIdentity?.type === "always",
|
|
1276
|
+
optional: (column) => !column.notNull || column.notNull && column.hasDefault,
|
|
1277
|
+
nullable: (column) => !column.notNull
|
|
1278
|
+
};
|
|
1279
|
+
var createSelectSchema = (entity, refine) => {
|
|
1280
|
+
if (isPgEnum(entity)) {
|
|
1281
|
+
return handleEnum(entity);
|
|
1282
|
+
}
|
|
1283
|
+
const columns = getColumns(entity);
|
|
1284
|
+
return handleColumns(columns, {}, selectConditions);
|
|
1285
|
+
};
|
|
1286
|
+
var createInsertSchema = (entity, refine) => {
|
|
1287
|
+
const columns = getColumns(entity);
|
|
1288
|
+
return handleColumns(columns, refine ?? {}, insertConditions);
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
// src/db/schema.ts
|
|
1292
|
+
var users = sqliteCore.sqliteTable("users", {
|
|
1293
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1294
|
+
email: sqliteCore.text("email").notNull().unique(),
|
|
1295
|
+
username: sqliteCore.text("username").notNull().unique(),
|
|
1296
|
+
firstName: sqliteCore.text("first_name").notNull(),
|
|
1297
|
+
lastName: sqliteCore.text("last_name").notNull(),
|
|
1298
|
+
passwordHash: sqliteCore.text("password_hash"),
|
|
1299
|
+
// Hashed password, nullable for OAuth users
|
|
1300
|
+
role: sqliteCore.text("role").notNull().default("viewer"),
|
|
1301
|
+
// 'admin', 'editor', 'author', 'viewer'
|
|
1302
|
+
avatar: sqliteCore.text("avatar"),
|
|
1303
|
+
isActive: sqliteCore.integer("is_active", { mode: "boolean" }).notNull().default(true),
|
|
1304
|
+
lastLoginAt: sqliteCore.integer("last_login_at"),
|
|
1305
|
+
createdAt: sqliteCore.integer("created_at").notNull(),
|
|
1306
|
+
updatedAt: sqliteCore.integer("updated_at").notNull()
|
|
1307
|
+
});
|
|
1308
|
+
var collections = sqliteCore.sqliteTable("collections", {
|
|
1309
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1310
|
+
name: sqliteCore.text("name").notNull().unique(),
|
|
1311
|
+
displayName: sqliteCore.text("display_name").notNull(),
|
|
1312
|
+
description: sqliteCore.text("description"),
|
|
1313
|
+
schema: sqliteCore.text("schema", { mode: "json" }).notNull(),
|
|
1314
|
+
// JSON schema definition
|
|
1315
|
+
isActive: sqliteCore.integer("is_active", { mode: "boolean" }).notNull().default(true),
|
|
1316
|
+
managed: sqliteCore.integer("managed", { mode: "boolean" }).notNull().default(false),
|
|
1317
|
+
// Config-managed collections cannot be edited in UI
|
|
1318
|
+
createdAt: sqliteCore.integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
1319
|
+
updatedAt: sqliteCore.integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
1320
|
+
});
|
|
1321
|
+
var content = sqliteCore.sqliteTable("content", {
|
|
1322
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1323
|
+
collectionId: sqliteCore.text("collection_id").notNull().references(() => collections.id),
|
|
1324
|
+
slug: sqliteCore.text("slug").notNull(),
|
|
1325
|
+
title: sqliteCore.text("title").notNull(),
|
|
1326
|
+
data: sqliteCore.text("data", { mode: "json" }).notNull(),
|
|
1327
|
+
// JSON content data
|
|
1328
|
+
status: sqliteCore.text("status").notNull().default("draft"),
|
|
1329
|
+
// 'draft', 'published', 'archived'
|
|
1330
|
+
publishedAt: sqliteCore.integer("published_at", { mode: "timestamp" }),
|
|
1331
|
+
authorId: sqliteCore.text("author_id").notNull().references(() => users.id),
|
|
1332
|
+
createdAt: sqliteCore.integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
1333
|
+
updatedAt: sqliteCore.integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
1334
|
+
});
|
|
1335
|
+
var contentVersions = sqliteCore.sqliteTable("content_versions", {
|
|
1336
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1337
|
+
contentId: sqliteCore.text("content_id").notNull().references(() => content.id),
|
|
1338
|
+
version: sqliteCore.integer("version").notNull(),
|
|
1339
|
+
data: sqliteCore.text("data", { mode: "json" }).notNull(),
|
|
1340
|
+
authorId: sqliteCore.text("author_id").notNull().references(() => users.id),
|
|
1341
|
+
createdAt: sqliteCore.integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
1342
|
+
});
|
|
1343
|
+
var media = sqliteCore.sqliteTable("media", {
|
|
1344
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1345
|
+
filename: sqliteCore.text("filename").notNull(),
|
|
1346
|
+
originalName: sqliteCore.text("original_name").notNull(),
|
|
1347
|
+
mimeType: sqliteCore.text("mime_type").notNull(),
|
|
1348
|
+
size: sqliteCore.integer("size").notNull(),
|
|
1349
|
+
width: sqliteCore.integer("width"),
|
|
1350
|
+
height: sqliteCore.integer("height"),
|
|
1351
|
+
folder: sqliteCore.text("folder").notNull().default("uploads"),
|
|
1352
|
+
r2Key: sqliteCore.text("r2_key").notNull(),
|
|
1353
|
+
// R2 storage key
|
|
1354
|
+
publicUrl: sqliteCore.text("public_url").notNull(),
|
|
1355
|
+
// CDN URL
|
|
1356
|
+
thumbnailUrl: sqliteCore.text("thumbnail_url"),
|
|
1357
|
+
alt: sqliteCore.text("alt"),
|
|
1358
|
+
caption: sqliteCore.text("caption"),
|
|
1359
|
+
tags: sqliteCore.text("tags", { mode: "json" }),
|
|
1360
|
+
// JSON array of tags
|
|
1361
|
+
uploadedBy: sqliteCore.text("uploaded_by").notNull().references(() => users.id),
|
|
1362
|
+
uploadedAt: sqliteCore.integer("uploaded_at").notNull(),
|
|
1363
|
+
updatedAt: sqliteCore.integer("updated_at"),
|
|
1364
|
+
publishedAt: sqliteCore.integer("published_at"),
|
|
1365
|
+
scheduledAt: sqliteCore.integer("scheduled_at"),
|
|
1366
|
+
archivedAt: sqliteCore.integer("archived_at"),
|
|
1367
|
+
deletedAt: sqliteCore.integer("deleted_at")
|
|
1368
|
+
});
|
|
1369
|
+
var apiTokens = sqliteCore.sqliteTable("api_tokens", {
|
|
1370
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1371
|
+
name: sqliteCore.text("name").notNull(),
|
|
1372
|
+
token: sqliteCore.text("token").notNull().unique(),
|
|
1373
|
+
userId: sqliteCore.text("user_id").notNull().references(() => users.id),
|
|
1374
|
+
permissions: sqliteCore.text("permissions", { mode: "json" }).notNull(),
|
|
1375
|
+
// Array of permissions
|
|
1376
|
+
expiresAt: sqliteCore.integer("expires_at", { mode: "timestamp" }),
|
|
1377
|
+
lastUsedAt: sqliteCore.integer("last_used_at", { mode: "timestamp" }),
|
|
1378
|
+
createdAt: sqliteCore.integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
1379
|
+
});
|
|
1380
|
+
var workflowHistory = sqliteCore.sqliteTable("workflow_history", {
|
|
1381
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1382
|
+
contentId: sqliteCore.text("content_id").notNull().references(() => content.id),
|
|
1383
|
+
action: sqliteCore.text("action").notNull(),
|
|
1384
|
+
fromStatus: sqliteCore.text("from_status").notNull(),
|
|
1385
|
+
toStatus: sqliteCore.text("to_status").notNull(),
|
|
1386
|
+
userId: sqliteCore.text("user_id").notNull().references(() => users.id),
|
|
1387
|
+
comment: sqliteCore.text("comment"),
|
|
1388
|
+
createdAt: sqliteCore.integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
1389
|
+
});
|
|
1390
|
+
var plugins = sqliteCore.sqliteTable("plugins", {
|
|
1391
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1392
|
+
name: sqliteCore.text("name").notNull().unique(),
|
|
1393
|
+
displayName: sqliteCore.text("display_name").notNull(),
|
|
1394
|
+
description: sqliteCore.text("description"),
|
|
1395
|
+
version: sqliteCore.text("version").notNull(),
|
|
1396
|
+
author: sqliteCore.text("author").notNull(),
|
|
1397
|
+
category: sqliteCore.text("category").notNull(),
|
|
1398
|
+
icon: sqliteCore.text("icon"),
|
|
1399
|
+
status: sqliteCore.text("status").notNull().default("inactive"),
|
|
1400
|
+
// 'active', 'inactive', 'error'
|
|
1401
|
+
isCore: sqliteCore.integer("is_core", { mode: "boolean" }).notNull().default(false),
|
|
1402
|
+
settings: sqliteCore.text("settings", { mode: "json" }),
|
|
1403
|
+
permissions: sqliteCore.text("permissions", { mode: "json" }),
|
|
1404
|
+
dependencies: sqliteCore.text("dependencies", { mode: "json" }),
|
|
1405
|
+
downloadCount: sqliteCore.integer("download_count").notNull().default(0),
|
|
1406
|
+
rating: sqliteCore.integer("rating").notNull().default(0),
|
|
1407
|
+
installedAt: sqliteCore.integer("installed_at").notNull(),
|
|
1408
|
+
activatedAt: sqliteCore.integer("activated_at"),
|
|
1409
|
+
lastUpdated: sqliteCore.integer("last_updated").notNull(),
|
|
1410
|
+
errorMessage: sqliteCore.text("error_message"),
|
|
1411
|
+
createdAt: sqliteCore.integer("created_at").notNull().$defaultFn(() => Math.floor(Date.now() / 1e3)),
|
|
1412
|
+
updatedAt: sqliteCore.integer("updated_at").notNull().$defaultFn(() => Math.floor(Date.now() / 1e3))
|
|
1413
|
+
});
|
|
1414
|
+
var pluginHooks = sqliteCore.sqliteTable("plugin_hooks", {
|
|
1415
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1416
|
+
pluginId: sqliteCore.text("plugin_id").notNull().references(() => plugins.id),
|
|
1417
|
+
hookName: sqliteCore.text("hook_name").notNull(),
|
|
1418
|
+
handlerName: sqliteCore.text("handler_name").notNull(),
|
|
1419
|
+
priority: sqliteCore.integer("priority").notNull().default(10),
|
|
1420
|
+
isActive: sqliteCore.integer("is_active", { mode: "boolean" }).notNull().default(true),
|
|
1421
|
+
createdAt: sqliteCore.integer("created_at").notNull().$defaultFn(() => Math.floor(Date.now() / 1e3))
|
|
1422
|
+
});
|
|
1423
|
+
var pluginRoutes = sqliteCore.sqliteTable("plugin_routes", {
|
|
1424
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1425
|
+
pluginId: sqliteCore.text("plugin_id").notNull().references(() => plugins.id),
|
|
1426
|
+
path: sqliteCore.text("path").notNull(),
|
|
1427
|
+
method: sqliteCore.text("method").notNull(),
|
|
1428
|
+
handlerName: sqliteCore.text("handler_name").notNull(),
|
|
1429
|
+
middleware: sqliteCore.text("middleware", { mode: "json" }),
|
|
1430
|
+
isActive: sqliteCore.integer("is_active", { mode: "boolean" }).notNull().default(true),
|
|
1431
|
+
createdAt: sqliteCore.integer("created_at").notNull().$defaultFn(() => Math.floor(Date.now() / 1e3))
|
|
1432
|
+
});
|
|
1433
|
+
var pluginAssets = sqliteCore.sqliteTable("plugin_assets", {
|
|
1434
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1435
|
+
pluginId: sqliteCore.text("plugin_id").notNull().references(() => plugins.id),
|
|
1436
|
+
assetType: sqliteCore.text("asset_type").notNull(),
|
|
1437
|
+
// 'css', 'js', 'image', 'font'
|
|
1438
|
+
assetPath: sqliteCore.text("asset_path").notNull(),
|
|
1439
|
+
loadOrder: sqliteCore.integer("load_order").notNull().default(100),
|
|
1440
|
+
loadLocation: sqliteCore.text("load_location").notNull().default("footer"),
|
|
1441
|
+
// 'header', 'footer'
|
|
1442
|
+
isActive: sqliteCore.integer("is_active", { mode: "boolean" }).notNull().default(true),
|
|
1443
|
+
createdAt: sqliteCore.integer("created_at").notNull().$defaultFn(() => Math.floor(Date.now() / 1e3))
|
|
1444
|
+
});
|
|
1445
|
+
var pluginActivityLog = sqliteCore.sqliteTable("plugin_activity_log", {
|
|
1446
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1447
|
+
pluginId: sqliteCore.text("plugin_id").notNull().references(() => plugins.id),
|
|
1448
|
+
action: sqliteCore.text("action").notNull(),
|
|
1449
|
+
userId: sqliteCore.text("user_id"),
|
|
1450
|
+
details: sqliteCore.text("details", { mode: "json" }),
|
|
1451
|
+
timestamp: sqliteCore.integer("timestamp").notNull().$defaultFn(() => Math.floor(Date.now() / 1e3))
|
|
1452
|
+
});
|
|
1453
|
+
var insertUserSchema = createInsertSchema(users, {
|
|
1454
|
+
email: (schema) => schema.email(),
|
|
1455
|
+
firstName: (schema) => schema.min(1),
|
|
1456
|
+
lastName: (schema) => schema.min(1),
|
|
1457
|
+
username: (schema) => schema.min(3)
|
|
1458
|
+
});
|
|
1459
|
+
var selectUserSchema = createSelectSchema(users);
|
|
1460
|
+
var insertCollectionSchema = createInsertSchema(collections, {
|
|
1461
|
+
name: (schema) => schema.min(1).regex(/^[a-z0-9_]+$/, "Collection name must be lowercase with underscores"),
|
|
1462
|
+
displayName: (schema) => schema.min(1)
|
|
1463
|
+
});
|
|
1464
|
+
var selectCollectionSchema = createSelectSchema(collections);
|
|
1465
|
+
var insertContentSchema = createInsertSchema(content, {
|
|
1466
|
+
slug: (schema) => schema.min(1).regex(/^[a-zA-Z0-9_-]+$/, "Slug must contain only letters, numbers, underscores, and hyphens"),
|
|
1467
|
+
title: (schema) => schema.min(1),
|
|
1468
|
+
status: (schema) => schema
|
|
1469
|
+
});
|
|
1470
|
+
var selectContentSchema = createSelectSchema(content);
|
|
1471
|
+
var insertMediaSchema = createInsertSchema(media, {
|
|
1472
|
+
filename: (schema) => schema.min(1),
|
|
1473
|
+
originalName: (schema) => schema.min(1),
|
|
1474
|
+
mimeType: (schema) => schema.min(1),
|
|
1475
|
+
size: (schema) => schema.positive(),
|
|
1476
|
+
r2Key: (schema) => schema.min(1),
|
|
1477
|
+
publicUrl: (schema) => schema.url(),
|
|
1478
|
+
folder: (schema) => schema.min(1)
|
|
1479
|
+
});
|
|
1480
|
+
var selectMediaSchema = createSelectSchema(media);
|
|
1481
|
+
var insertWorkflowHistorySchema = createInsertSchema(workflowHistory, {
|
|
1482
|
+
action: (schema) => schema.min(1),
|
|
1483
|
+
fromStatus: (schema) => schema.min(1),
|
|
1484
|
+
toStatus: (schema) => schema.min(1)
|
|
1485
|
+
});
|
|
1486
|
+
var selectWorkflowHistorySchema = createSelectSchema(workflowHistory);
|
|
1487
|
+
var insertPluginSchema = createInsertSchema(plugins, {
|
|
1488
|
+
name: (schema) => schema.min(1),
|
|
1489
|
+
displayName: (schema) => schema.min(1),
|
|
1490
|
+
version: (schema) => schema.min(1),
|
|
1491
|
+
author: (schema) => schema.min(1),
|
|
1492
|
+
category: (schema) => schema.min(1)
|
|
1493
|
+
});
|
|
1494
|
+
var selectPluginSchema = createSelectSchema(plugins);
|
|
1495
|
+
var insertPluginHookSchema = createInsertSchema(pluginHooks, {
|
|
1496
|
+
hookName: (schema) => schema.min(1),
|
|
1497
|
+
handlerName: (schema) => schema.min(1)
|
|
1498
|
+
});
|
|
1499
|
+
var selectPluginHookSchema = createSelectSchema(pluginHooks);
|
|
1500
|
+
var insertPluginRouteSchema = createInsertSchema(pluginRoutes, {
|
|
1501
|
+
path: (schema) => schema.min(1),
|
|
1502
|
+
method: (schema) => schema.min(1),
|
|
1503
|
+
handlerName: (schema) => schema.min(1)
|
|
1504
|
+
});
|
|
1505
|
+
var selectPluginRouteSchema = createSelectSchema(pluginRoutes);
|
|
1506
|
+
var insertPluginAssetSchema = createInsertSchema(pluginAssets, {
|
|
1507
|
+
assetType: (schema) => schema.min(1),
|
|
1508
|
+
assetPath: (schema) => schema.min(1)
|
|
1509
|
+
});
|
|
1510
|
+
var selectPluginAssetSchema = createSelectSchema(pluginAssets);
|
|
1511
|
+
var insertPluginActivityLogSchema = createInsertSchema(pluginActivityLog, {
|
|
1512
|
+
action: (schema) => schema.min(1)
|
|
1513
|
+
});
|
|
1514
|
+
var selectPluginActivityLogSchema = createSelectSchema(pluginActivityLog);
|
|
1515
|
+
var systemLogs = sqliteCore.sqliteTable("system_logs", {
|
|
1516
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1517
|
+
level: sqliteCore.text("level").notNull(),
|
|
1518
|
+
// 'debug', 'info', 'warn', 'error', 'fatal'
|
|
1519
|
+
category: sqliteCore.text("category").notNull(),
|
|
1520
|
+
// 'auth', 'api', 'workflow', 'plugin', 'media', 'system', etc.
|
|
1521
|
+
message: sqliteCore.text("message").notNull(),
|
|
1522
|
+
data: sqliteCore.text("data", { mode: "json" }),
|
|
1523
|
+
// Additional structured data
|
|
1524
|
+
userId: sqliteCore.text("user_id").references(() => users.id),
|
|
1525
|
+
sessionId: sqliteCore.text("session_id"),
|
|
1526
|
+
requestId: sqliteCore.text("request_id"),
|
|
1527
|
+
ipAddress: sqliteCore.text("ip_address"),
|
|
1528
|
+
userAgent: sqliteCore.text("user_agent"),
|
|
1529
|
+
method: sqliteCore.text("method"),
|
|
1530
|
+
// HTTP method for API logs
|
|
1531
|
+
url: sqliteCore.text("url"),
|
|
1532
|
+
// Request URL for API logs
|
|
1533
|
+
statusCode: sqliteCore.integer("status_code"),
|
|
1534
|
+
// HTTP status code for API logs
|
|
1535
|
+
duration: sqliteCore.integer("duration"),
|
|
1536
|
+
// Request duration in milliseconds
|
|
1537
|
+
stackTrace: sqliteCore.text("stack_trace"),
|
|
1538
|
+
// Error stack trace for error logs
|
|
1539
|
+
tags: sqliteCore.text("tags", { mode: "json" }),
|
|
1540
|
+
// Array of tags for categorization
|
|
1541
|
+
source: sqliteCore.text("source"),
|
|
1542
|
+
// Source component/module that generated the log
|
|
1543
|
+
createdAt: sqliteCore.integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
1544
|
+
});
|
|
1545
|
+
var logConfig = sqliteCore.sqliteTable("log_config", {
|
|
1546
|
+
id: sqliteCore.text("id").primaryKey(),
|
|
1547
|
+
category: sqliteCore.text("category").notNull().unique(),
|
|
1548
|
+
enabled: sqliteCore.integer("enabled", { mode: "boolean" }).notNull().default(true),
|
|
1549
|
+
level: sqliteCore.text("level").notNull().default("info"),
|
|
1550
|
+
// minimum log level to store
|
|
1551
|
+
retention: sqliteCore.integer("retention").notNull().default(30),
|
|
1552
|
+
// days to keep logs
|
|
1553
|
+
maxSize: sqliteCore.integer("max_size").default(1e4),
|
|
1554
|
+
// max number of logs per category
|
|
1555
|
+
createdAt: sqliteCore.integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
1556
|
+
updatedAt: sqliteCore.integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
1557
|
+
});
|
|
1558
|
+
var insertSystemLogSchema = createInsertSchema(systemLogs, {
|
|
1559
|
+
level: (schema) => schema.min(1),
|
|
1560
|
+
category: (schema) => schema.min(1),
|
|
1561
|
+
message: (schema) => schema.min(1)
|
|
1562
|
+
});
|
|
1563
|
+
var selectSystemLogSchema = createSelectSchema(systemLogs);
|
|
1564
|
+
var insertLogConfigSchema = createInsertSchema(logConfig, {
|
|
1565
|
+
category: (schema) => schema.min(1),
|
|
1566
|
+
level: (schema) => schema.min(1)
|
|
1567
|
+
});
|
|
1568
|
+
var selectLogConfigSchema = createSelectSchema(logConfig);
|
|
1569
|
+
var Logger = class {
|
|
1570
|
+
db;
|
|
1571
|
+
enabled = true;
|
|
1572
|
+
configCache = /* @__PURE__ */ new Map();
|
|
1573
|
+
lastConfigRefresh = 0;
|
|
1574
|
+
configRefreshInterval = 6e4;
|
|
1575
|
+
// 1 minute
|
|
1576
|
+
constructor(database) {
|
|
1577
|
+
this.db = d1.drizzle(database);
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Log a debug message
|
|
1581
|
+
*/
|
|
1582
|
+
async debug(category, message, data, context) {
|
|
1583
|
+
return this.log("debug", category, message, data, context);
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Log an info message
|
|
1587
|
+
*/
|
|
1588
|
+
async info(category, message, data, context) {
|
|
1589
|
+
return this.log("info", category, message, data, context);
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Log a warning message
|
|
1593
|
+
*/
|
|
1594
|
+
async warn(category, message, data, context) {
|
|
1595
|
+
return this.log("warn", category, message, data, context);
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Log an error message
|
|
1599
|
+
*/
|
|
1600
|
+
async error(category, message, error, context) {
|
|
1601
|
+
const errorData = error instanceof Error ? {
|
|
1602
|
+
name: error.name,
|
|
1603
|
+
message: error.message,
|
|
1604
|
+
stack: error.stack
|
|
1605
|
+
} : error;
|
|
1606
|
+
return this.log("error", category, message, errorData, {
|
|
1607
|
+
...context,
|
|
1608
|
+
stackTrace: error instanceof Error ? error.stack : void 0
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Log a fatal message
|
|
1613
|
+
*/
|
|
1614
|
+
async fatal(category, message, error, context) {
|
|
1615
|
+
const errorData = error instanceof Error ? {
|
|
1616
|
+
name: error.name,
|
|
1617
|
+
message: error.message,
|
|
1618
|
+
stack: error.stack
|
|
1619
|
+
} : error;
|
|
1620
|
+
return this.log("fatal", category, message, errorData, {
|
|
1621
|
+
...context,
|
|
1622
|
+
stackTrace: error instanceof Error ? error.stack : void 0
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Log an API request
|
|
1627
|
+
*/
|
|
1628
|
+
async logRequest(method, url, statusCode, duration, context) {
|
|
1629
|
+
const level = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "info";
|
|
1630
|
+
return this.log(level, "api", `${method} ${url} - ${statusCode}`, {
|
|
1631
|
+
method,
|
|
1632
|
+
url,
|
|
1633
|
+
statusCode,
|
|
1634
|
+
duration
|
|
1635
|
+
}, {
|
|
1636
|
+
...context,
|
|
1637
|
+
method,
|
|
1638
|
+
url,
|
|
1639
|
+
statusCode,
|
|
1640
|
+
duration
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Log an authentication event
|
|
1645
|
+
*/
|
|
1646
|
+
async logAuth(action, userId, success = true, context) {
|
|
1647
|
+
const level = success ? "info" : "warn";
|
|
1648
|
+
return this.log(level, "auth", `Authentication ${action}: ${success ? "success" : "failed"}`, {
|
|
1649
|
+
action,
|
|
1650
|
+
success,
|
|
1651
|
+
userId
|
|
1652
|
+
}, {
|
|
1653
|
+
...context,
|
|
1654
|
+
userId,
|
|
1655
|
+
tags: ["authentication", action]
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Log a security event
|
|
1660
|
+
*/
|
|
1661
|
+
async logSecurity(event, severity, context) {
|
|
1662
|
+
const level = severity === "critical" ? "fatal" : severity === "high" ? "error" : "warn";
|
|
1663
|
+
return this.log(level, "security", `Security event: ${event}`, {
|
|
1664
|
+
event,
|
|
1665
|
+
severity
|
|
1666
|
+
}, {
|
|
1667
|
+
...context,
|
|
1668
|
+
tags: ["security", severity]
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Core logging method
|
|
1673
|
+
*/
|
|
1674
|
+
async log(level, category, message, data, context) {
|
|
1675
|
+
if (!this.enabled) return;
|
|
1676
|
+
try {
|
|
1677
|
+
const config = await this.getConfig(category);
|
|
1678
|
+
if (!config || !config.enabled || !this.shouldLog(level, config.level)) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const logEntry = {
|
|
1682
|
+
id: crypto.randomUUID(),
|
|
1683
|
+
level,
|
|
1684
|
+
category,
|
|
1685
|
+
message,
|
|
1686
|
+
data: data ? JSON.stringify(data) : null,
|
|
1687
|
+
userId: context?.userId || null,
|
|
1688
|
+
sessionId: context?.sessionId || null,
|
|
1689
|
+
requestId: context?.requestId || null,
|
|
1690
|
+
ipAddress: context?.ipAddress || null,
|
|
1691
|
+
userAgent: context?.userAgent || null,
|
|
1692
|
+
method: context?.method || null,
|
|
1693
|
+
url: context?.url || null,
|
|
1694
|
+
statusCode: context?.statusCode || null,
|
|
1695
|
+
duration: context?.duration || null,
|
|
1696
|
+
stackTrace: context?.stackTrace || null,
|
|
1697
|
+
tags: context?.tags ? JSON.stringify(context.tags) : null,
|
|
1698
|
+
source: context?.source || null,
|
|
1699
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1700
|
+
};
|
|
1701
|
+
await this.db.insert(systemLogs).values(logEntry);
|
|
1702
|
+
if (config.maxSize) {
|
|
1703
|
+
await this.cleanupCategory(category, config.maxSize);
|
|
1704
|
+
}
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
console.error("Logger error:", error);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Get logs with filtering and pagination
|
|
1711
|
+
*/
|
|
1712
|
+
async getLogs(filter = {}) {
|
|
1713
|
+
try {
|
|
1714
|
+
const conditions = [];
|
|
1715
|
+
if (filter.level && filter.level.length > 0) {
|
|
1716
|
+
conditions.push(drizzleOrm.inArray(systemLogs.level, filter.level));
|
|
1717
|
+
}
|
|
1718
|
+
if (filter.category && filter.category.length > 0) {
|
|
1719
|
+
conditions.push(drizzleOrm.inArray(systemLogs.category, filter.category));
|
|
1720
|
+
}
|
|
1721
|
+
if (filter.userId) {
|
|
1722
|
+
conditions.push(drizzleOrm.eq(systemLogs.userId, filter.userId));
|
|
1723
|
+
}
|
|
1724
|
+
if (filter.source) {
|
|
1725
|
+
conditions.push(drizzleOrm.eq(systemLogs.source, filter.source));
|
|
1726
|
+
}
|
|
1727
|
+
if (filter.search) {
|
|
1728
|
+
conditions.push(
|
|
1729
|
+
drizzleOrm.like(systemLogs.message, `%${filter.search}%`)
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (filter.startDate) {
|
|
1733
|
+
conditions.push(drizzleOrm.gte(systemLogs.createdAt, filter.startDate));
|
|
1734
|
+
}
|
|
1735
|
+
if (filter.endDate) {
|
|
1736
|
+
conditions.push(drizzleOrm.lte(systemLogs.createdAt, filter.endDate));
|
|
1737
|
+
}
|
|
1738
|
+
const whereClause = conditions.length > 0 ? drizzleOrm.and(...conditions) : void 0;
|
|
1739
|
+
const totalResult = await this.db.select({ count: drizzleOrm.count() }).from(systemLogs).where(whereClause);
|
|
1740
|
+
const total = totalResult[0]?.count || 0;
|
|
1741
|
+
const sortColumn = filter.sortBy === "level" ? systemLogs.level : filter.sortBy === "category" ? systemLogs.category : systemLogs.createdAt;
|
|
1742
|
+
const sortFn = filter.sortOrder === "asc" ? drizzleOrm.asc : drizzleOrm.desc;
|
|
1743
|
+
const logs = await this.db.select().from(systemLogs).where(whereClause).orderBy(sortFn(sortColumn)).limit(filter.limit || 50).offset(filter.offset || 0);
|
|
1744
|
+
return { logs, total };
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
console.error("Error getting logs:", error);
|
|
1747
|
+
return { logs: [], total: 0 };
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Get log configuration for a category
|
|
1752
|
+
*/
|
|
1753
|
+
async getConfig(category) {
|
|
1754
|
+
try {
|
|
1755
|
+
const now = Date.now();
|
|
1756
|
+
if (this.configCache.has(category) && now - this.lastConfigRefresh < this.configRefreshInterval) {
|
|
1757
|
+
return this.configCache.get(category) || null;
|
|
1758
|
+
}
|
|
1759
|
+
const configs = await this.db.select().from(logConfig).where(drizzleOrm.eq(logConfig.category, category));
|
|
1760
|
+
const config = configs[0] || null;
|
|
1761
|
+
if (config) {
|
|
1762
|
+
this.configCache.set(category, config);
|
|
1763
|
+
this.lastConfigRefresh = now;
|
|
1764
|
+
}
|
|
1765
|
+
return config;
|
|
1766
|
+
} catch (error) {
|
|
1767
|
+
console.error("Error getting log config:", error);
|
|
1768
|
+
return null;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Update log configuration
|
|
1773
|
+
*/
|
|
1774
|
+
async updateConfig(category, updates) {
|
|
1775
|
+
try {
|
|
1776
|
+
await this.db.update(logConfig).set({
|
|
1777
|
+
...updates,
|
|
1778
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1779
|
+
}).where(drizzleOrm.eq(logConfig.category, category));
|
|
1780
|
+
this.configCache.delete(category);
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
console.error("Error updating log config:", error);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Get all log configurations
|
|
1787
|
+
*/
|
|
1788
|
+
async getAllConfigs() {
|
|
1789
|
+
try {
|
|
1790
|
+
return await this.db.select().from(logConfig);
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
console.error("Error getting log configs:", error);
|
|
1793
|
+
return [];
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Clean up old logs for a category
|
|
1798
|
+
*/
|
|
1799
|
+
async cleanupCategory(category, maxSize) {
|
|
1800
|
+
try {
|
|
1801
|
+
const countResult = await this.db.select({ count: drizzleOrm.count() }).from(systemLogs).where(drizzleOrm.eq(systemLogs.category, category));
|
|
1802
|
+
const currentCount = countResult[0]?.count || 0;
|
|
1803
|
+
if (currentCount > maxSize) {
|
|
1804
|
+
const cutoffLogs = await this.db.select({ createdAt: systemLogs.createdAt }).from(systemLogs).where(drizzleOrm.eq(systemLogs.category, category)).orderBy(drizzleOrm.desc(systemLogs.createdAt)).limit(1).offset(maxSize - 1);
|
|
1805
|
+
if (cutoffLogs[0]) {
|
|
1806
|
+
await this.db.delete(systemLogs).where(
|
|
1807
|
+
drizzleOrm.and(
|
|
1808
|
+
drizzleOrm.eq(systemLogs.category, category),
|
|
1809
|
+
drizzleOrm.lte(systemLogs.createdAt, cutoffLogs[0].createdAt)
|
|
1810
|
+
)
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
console.error("Error cleaning up logs:", error);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Clean up logs based on retention policy
|
|
1820
|
+
*/
|
|
1821
|
+
async cleanupByRetention() {
|
|
1822
|
+
try {
|
|
1823
|
+
const configs = await this.getAllConfigs();
|
|
1824
|
+
for (const config of configs) {
|
|
1825
|
+
if (config.retention > 0) {
|
|
1826
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
1827
|
+
cutoffDate.setDate(cutoffDate.getDate() - config.retention);
|
|
1828
|
+
await this.db.delete(systemLogs).where(
|
|
1829
|
+
drizzleOrm.and(
|
|
1830
|
+
drizzleOrm.eq(systemLogs.category, config.category),
|
|
1831
|
+
drizzleOrm.lte(systemLogs.createdAt, cutoffDate)
|
|
1832
|
+
)
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
console.error("Error cleaning up logs by retention:", error);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Check if a log level should be recorded based on configuration
|
|
1842
|
+
*/
|
|
1843
|
+
shouldLog(level, configLevel) {
|
|
1844
|
+
const levels = ["debug", "info", "warn", "error", "fatal"];
|
|
1845
|
+
const levelIndex = levels.indexOf(level);
|
|
1846
|
+
const configLevelIndex = levels.indexOf(configLevel);
|
|
1847
|
+
return levelIndex >= configLevelIndex;
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Enable or disable logging
|
|
1851
|
+
*/
|
|
1852
|
+
setEnabled(enabled) {
|
|
1853
|
+
this.enabled = enabled;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Check if logging is enabled
|
|
1857
|
+
*/
|
|
1858
|
+
isEnabled() {
|
|
1859
|
+
return this.enabled;
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
var loggerInstance = null;
|
|
1863
|
+
function getLogger(database) {
|
|
1864
|
+
if (!loggerInstance && database) {
|
|
1865
|
+
loggerInstance = new Logger(database);
|
|
1866
|
+
}
|
|
1867
|
+
if (!loggerInstance) {
|
|
1868
|
+
throw new Error("Logger not initialized. Call getLogger with a database instance first.");
|
|
1869
|
+
}
|
|
1870
|
+
return loggerInstance;
|
|
1871
|
+
}
|
|
1872
|
+
function initLogger(database) {
|
|
1873
|
+
loggerInstance = new Logger(database);
|
|
1874
|
+
return loggerInstance;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// src/services/plugin-service.ts
|
|
1878
|
+
var PluginService = class {
|
|
1879
|
+
constructor(db) {
|
|
1880
|
+
this.db = db;
|
|
1881
|
+
}
|
|
1882
|
+
async getAllPlugins() {
|
|
1883
|
+
await this.ensureAllPluginsExist();
|
|
1884
|
+
const stmt = this.db.prepare(`
|
|
1885
|
+
SELECT * FROM plugins
|
|
1886
|
+
ORDER BY is_core DESC, display_name ASC
|
|
1887
|
+
`);
|
|
1888
|
+
const { results } = await stmt.all();
|
|
1889
|
+
return (results || []).map(this.mapPluginFromDb);
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Ensure all plugins from the registry exist in the database
|
|
1893
|
+
* Auto-installs any newly detected plugins with inactive status
|
|
1894
|
+
*
|
|
1895
|
+
* Note: This method should be overridden or configured with a plugin registry
|
|
1896
|
+
* in the consuming application
|
|
1897
|
+
*/
|
|
1898
|
+
async ensureAllPluginsExist() {
|
|
1899
|
+
console.log("[PluginService] ensureAllPluginsExist - requires PLUGIN_REGISTRY configuration");
|
|
1900
|
+
}
|
|
1901
|
+
async getPlugin(pluginId) {
|
|
1902
|
+
const stmt = this.db.prepare("SELECT * FROM plugins WHERE id = ?");
|
|
1903
|
+
const plugin = await stmt.bind(pluginId).first();
|
|
1904
|
+
if (!plugin) return null;
|
|
1905
|
+
return this.mapPluginFromDb(plugin);
|
|
1906
|
+
}
|
|
1907
|
+
async getPluginByName(name) {
|
|
1908
|
+
const stmt = this.db.prepare("SELECT * FROM plugins WHERE name = ?");
|
|
1909
|
+
const plugin = await stmt.bind(name).first();
|
|
1910
|
+
if (!plugin) return null;
|
|
1911
|
+
return this.mapPluginFromDb(plugin);
|
|
1912
|
+
}
|
|
1913
|
+
async getPluginStats() {
|
|
1914
|
+
const stmt = this.db.prepare(`
|
|
1915
|
+
SELECT
|
|
1916
|
+
COUNT(*) as total,
|
|
1917
|
+
COUNT(CASE WHEN status = 'active' THEN 1 END) as active,
|
|
1918
|
+
COUNT(CASE WHEN status = 'inactive' THEN 1 END) as inactive,
|
|
1919
|
+
COUNT(CASE WHEN status = 'error' THEN 1 END) as errors
|
|
1920
|
+
FROM plugins
|
|
1921
|
+
`);
|
|
1922
|
+
const stats = await stmt.first();
|
|
1923
|
+
return {
|
|
1924
|
+
total: stats.total || 0,
|
|
1925
|
+
active: stats.active || 0,
|
|
1926
|
+
inactive: stats.inactive || 0,
|
|
1927
|
+
errors: stats.errors || 0
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
async installPlugin(pluginData) {
|
|
1931
|
+
const id = pluginData.id || `plugin-${Date.now()}`;
|
|
1932
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1933
|
+
const stmt = this.db.prepare(`
|
|
1934
|
+
INSERT INTO plugins (
|
|
1935
|
+
id, name, display_name, description, version, author, category, icon,
|
|
1936
|
+
status, is_core, settings, permissions, dependencies, download_count,
|
|
1937
|
+
rating, installed_at, last_updated
|
|
1938
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1939
|
+
`);
|
|
1940
|
+
await stmt.bind(
|
|
1941
|
+
id,
|
|
1942
|
+
pluginData.name || id,
|
|
1943
|
+
pluginData.display_name || "Unnamed Plugin",
|
|
1944
|
+
pluginData.description || "",
|
|
1945
|
+
pluginData.version || "1.0.0",
|
|
1946
|
+
pluginData.author || "Unknown",
|
|
1947
|
+
pluginData.category || "utilities",
|
|
1948
|
+
pluginData.icon || "\u{1F50C}",
|
|
1949
|
+
"inactive",
|
|
1950
|
+
pluginData.is_core || false,
|
|
1951
|
+
JSON.stringify(pluginData.settings || {}),
|
|
1952
|
+
JSON.stringify(pluginData.permissions || []),
|
|
1953
|
+
JSON.stringify(pluginData.dependencies || []),
|
|
1954
|
+
pluginData.download_count || 0,
|
|
1955
|
+
pluginData.rating || 0,
|
|
1956
|
+
now,
|
|
1957
|
+
now
|
|
1958
|
+
).run();
|
|
1959
|
+
await this.logActivity(id, "installed", null, { version: pluginData.version });
|
|
1960
|
+
const installed = await this.getPlugin(id);
|
|
1961
|
+
if (!installed) throw new Error("Failed to install plugin");
|
|
1962
|
+
return installed;
|
|
1963
|
+
}
|
|
1964
|
+
async uninstallPlugin(pluginId) {
|
|
1965
|
+
const plugin = await this.getPlugin(pluginId);
|
|
1966
|
+
if (!plugin) throw new Error("Plugin not found");
|
|
1967
|
+
if (plugin.is_core) throw new Error("Cannot uninstall core plugins");
|
|
1968
|
+
if (plugin.status === "active") {
|
|
1969
|
+
await this.deactivatePlugin(pluginId);
|
|
1970
|
+
}
|
|
1971
|
+
const stmt = this.db.prepare("DELETE FROM plugins WHERE id = ?");
|
|
1972
|
+
await stmt.bind(pluginId).run();
|
|
1973
|
+
await this.logActivity(pluginId, "uninstalled", null, { name: plugin.name });
|
|
1974
|
+
}
|
|
1975
|
+
async activatePlugin(pluginId) {
|
|
1976
|
+
const plugin = await this.getPlugin(pluginId);
|
|
1977
|
+
if (!plugin) throw new Error("Plugin not found");
|
|
1978
|
+
if (plugin.dependencies && plugin.dependencies.length > 0) {
|
|
1979
|
+
await this.checkDependencies(plugin.dependencies);
|
|
1980
|
+
}
|
|
1981
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1982
|
+
const stmt = this.db.prepare(`
|
|
1983
|
+
UPDATE plugins
|
|
1984
|
+
SET status = 'active', activated_at = ?, error_message = NULL
|
|
1985
|
+
WHERE id = ?
|
|
1986
|
+
`);
|
|
1987
|
+
await stmt.bind(now, pluginId).run();
|
|
1988
|
+
await this.logActivity(pluginId, "activated", null);
|
|
1989
|
+
}
|
|
1990
|
+
async deactivatePlugin(pluginId) {
|
|
1991
|
+
const plugin = await this.getPlugin(pluginId);
|
|
1992
|
+
if (!plugin) throw new Error("Plugin not found");
|
|
1993
|
+
await this.checkDependents(plugin.name);
|
|
1994
|
+
const stmt = this.db.prepare(`
|
|
1995
|
+
UPDATE plugins
|
|
1996
|
+
SET status = 'inactive', activated_at = NULL
|
|
1997
|
+
WHERE id = ?
|
|
1998
|
+
`);
|
|
1999
|
+
await stmt.bind(pluginId).run();
|
|
2000
|
+
await this.logActivity(pluginId, "deactivated", null);
|
|
2001
|
+
}
|
|
2002
|
+
async updatePluginSettings(pluginId, settings) {
|
|
2003
|
+
const plugin = await this.getPlugin(pluginId);
|
|
2004
|
+
if (!plugin) throw new Error("Plugin not found");
|
|
2005
|
+
const stmt = this.db.prepare(`
|
|
2006
|
+
UPDATE plugins
|
|
2007
|
+
SET settings = ?, updated_at = unixepoch()
|
|
2008
|
+
WHERE id = ?
|
|
2009
|
+
`);
|
|
2010
|
+
await stmt.bind(JSON.stringify(settings), pluginId).run();
|
|
2011
|
+
await this.logActivity(pluginId, "settings_updated", null);
|
|
2012
|
+
}
|
|
2013
|
+
async setPluginError(pluginId, error) {
|
|
2014
|
+
const stmt = this.db.prepare(`
|
|
2015
|
+
UPDATE plugins
|
|
2016
|
+
SET status = 'error', error_message = ?
|
|
2017
|
+
WHERE id = ?
|
|
2018
|
+
`);
|
|
2019
|
+
await stmt.bind(error, pluginId).run();
|
|
2020
|
+
await this.logActivity(pluginId, "error", null, { error });
|
|
2021
|
+
}
|
|
2022
|
+
async getPluginActivity(pluginId, limit = 10) {
|
|
2023
|
+
const stmt = this.db.prepare(`
|
|
2024
|
+
SELECT * FROM plugin_activity_log
|
|
2025
|
+
WHERE plugin_id = ?
|
|
2026
|
+
ORDER BY timestamp DESC
|
|
2027
|
+
LIMIT ?
|
|
2028
|
+
`);
|
|
2029
|
+
const { results } = await stmt.bind(pluginId, limit).all();
|
|
2030
|
+
return (results || []).map((row) => ({
|
|
2031
|
+
id: row.id,
|
|
2032
|
+
action: row.action,
|
|
2033
|
+
userId: row.user_id,
|
|
2034
|
+
details: row.details ? JSON.parse(row.details) : null,
|
|
2035
|
+
timestamp: row.timestamp
|
|
2036
|
+
}));
|
|
2037
|
+
}
|
|
2038
|
+
async registerHook(pluginId, hookName, handlerName, priority = 10) {
|
|
2039
|
+
const id = `hook-${Date.now()}`;
|
|
2040
|
+
const stmt = this.db.prepare(`
|
|
2041
|
+
INSERT INTO plugin_hooks (id, plugin_id, hook_name, handler_name, priority)
|
|
2042
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2043
|
+
`);
|
|
2044
|
+
await stmt.bind(id, pluginId, hookName, handlerName, priority).run();
|
|
2045
|
+
}
|
|
2046
|
+
async registerRoute(pluginId, path, method, handlerName, middleware) {
|
|
2047
|
+
const id = `route-${Date.now()}`;
|
|
2048
|
+
const stmt = this.db.prepare(`
|
|
2049
|
+
INSERT INTO plugin_routes (id, plugin_id, path, method, handler_name, middleware)
|
|
2050
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2051
|
+
`);
|
|
2052
|
+
await stmt.bind(
|
|
2053
|
+
id,
|
|
2054
|
+
pluginId,
|
|
2055
|
+
path,
|
|
2056
|
+
method,
|
|
2057
|
+
handlerName,
|
|
2058
|
+
JSON.stringify(middleware || [])
|
|
2059
|
+
).run();
|
|
2060
|
+
}
|
|
2061
|
+
async getPluginHooks(pluginId) {
|
|
2062
|
+
const stmt = this.db.prepare(`
|
|
2063
|
+
SELECT * FROM plugin_hooks
|
|
2064
|
+
WHERE plugin_id = ? AND is_active = TRUE
|
|
2065
|
+
ORDER BY priority ASC
|
|
2066
|
+
`);
|
|
2067
|
+
const { results } = await stmt.bind(pluginId).all();
|
|
2068
|
+
return results || [];
|
|
2069
|
+
}
|
|
2070
|
+
async getPluginRoutes(pluginId) {
|
|
2071
|
+
const stmt = this.db.prepare(`
|
|
2072
|
+
SELECT * FROM plugin_routes
|
|
2073
|
+
WHERE plugin_id = ? AND is_active = TRUE
|
|
2074
|
+
`);
|
|
2075
|
+
const { results } = await stmt.bind(pluginId).all();
|
|
2076
|
+
return results || [];
|
|
2077
|
+
}
|
|
2078
|
+
async checkDependencies(dependencies) {
|
|
2079
|
+
for (const dep of dependencies) {
|
|
2080
|
+
const plugin = await this.getPluginByName(dep);
|
|
2081
|
+
if (!plugin || plugin.status !== "active") {
|
|
2082
|
+
throw new Error(`Required dependency '${dep}' is not active`);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
async checkDependents(pluginName) {
|
|
2087
|
+
const stmt = this.db.prepare(`
|
|
2088
|
+
SELECT id, display_name FROM plugins
|
|
2089
|
+
WHERE status = 'active'
|
|
2090
|
+
AND dependencies LIKE ?
|
|
2091
|
+
`);
|
|
2092
|
+
const { results } = await stmt.bind(`%"${pluginName}"%`).all();
|
|
2093
|
+
if (results && results.length > 0) {
|
|
2094
|
+
const names = results.map((p) => p.display_name).join(", ");
|
|
2095
|
+
throw new Error(`Cannot deactivate. The following plugins depend on this one: ${names}`);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
async logActivity(pluginId, action, userId, details) {
|
|
2099
|
+
const id = `activity-${Date.now()}`;
|
|
2100
|
+
const stmt = this.db.prepare(`
|
|
2101
|
+
INSERT INTO plugin_activity_log (id, plugin_id, action, user_id, details)
|
|
2102
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2103
|
+
`);
|
|
2104
|
+
await stmt.bind(
|
|
2105
|
+
id,
|
|
2106
|
+
pluginId,
|
|
2107
|
+
action,
|
|
2108
|
+
userId,
|
|
2109
|
+
details ? JSON.stringify(details) : null
|
|
2110
|
+
).run();
|
|
2111
|
+
}
|
|
2112
|
+
mapPluginFromDb(row) {
|
|
2113
|
+
return {
|
|
2114
|
+
id: row.id,
|
|
2115
|
+
name: row.name,
|
|
2116
|
+
display_name: row.display_name,
|
|
2117
|
+
description: row.description,
|
|
2118
|
+
version: row.version,
|
|
2119
|
+
author: row.author,
|
|
2120
|
+
category: row.category,
|
|
2121
|
+
icon: row.icon,
|
|
2122
|
+
status: row.status,
|
|
2123
|
+
is_core: row.is_core === 1,
|
|
2124
|
+
settings: row.settings ? JSON.parse(row.settings) : void 0,
|
|
2125
|
+
permissions: row.permissions ? JSON.parse(row.permissions) : void 0,
|
|
2126
|
+
dependencies: row.dependencies ? JSON.parse(row.dependencies) : void 0,
|
|
2127
|
+
download_count: row.download_count || 0,
|
|
2128
|
+
rating: row.rating || 0,
|
|
2129
|
+
installed_at: row.installed_at,
|
|
2130
|
+
activated_at: row.activated_at,
|
|
2131
|
+
last_updated: row.last_updated,
|
|
2132
|
+
error_message: row.error_message
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
|
|
2137
|
+
// src/services/plugin-bootstrap.ts
|
|
2138
|
+
var PluginBootstrapService = class {
|
|
2139
|
+
constructor(db) {
|
|
2140
|
+
this.db = db;
|
|
2141
|
+
this.pluginService = new PluginService(db);
|
|
2142
|
+
}
|
|
2143
|
+
pluginService;
|
|
2144
|
+
/**
|
|
2145
|
+
* Core plugins that should always be available in the system
|
|
2146
|
+
*/
|
|
2147
|
+
CORE_PLUGINS = [
|
|
2148
|
+
{
|
|
2149
|
+
id: "core-auth",
|
|
2150
|
+
name: "core-auth",
|
|
2151
|
+
display_name: "Authentication System",
|
|
2152
|
+
description: "Core authentication and user management system",
|
|
2153
|
+
version: "1.0.0",
|
|
2154
|
+
author: "SonicJS Team",
|
|
2155
|
+
category: "security",
|
|
2156
|
+
icon: "\u{1F510}",
|
|
2157
|
+
permissions: ["manage:users", "manage:roles", "manage:permissions"],
|
|
2158
|
+
dependencies: [],
|
|
2159
|
+
settings: {
|
|
2160
|
+
requiredFields: {
|
|
2161
|
+
email: { required: true, minLength: 5, label: "Email", type: "email" },
|
|
2162
|
+
password: { required: true, minLength: 8, label: "Password", type: "password" },
|
|
2163
|
+
username: { required: true, minLength: 3, label: "Username", type: "text" },
|
|
2164
|
+
firstName: { required: true, minLength: 1, label: "First Name", type: "text" },
|
|
2165
|
+
lastName: { required: true, minLength: 1, label: "Last Name", type: "text" }
|
|
2166
|
+
},
|
|
2167
|
+
validation: {
|
|
2168
|
+
emailFormat: true,
|
|
2169
|
+
allowDuplicateUsernames: false,
|
|
2170
|
+
passwordRequirements: {
|
|
2171
|
+
requireUppercase: false,
|
|
2172
|
+
requireLowercase: false,
|
|
2173
|
+
requireNumbers: false,
|
|
2174
|
+
requireSpecialChars: false
|
|
2175
|
+
}
|
|
2176
|
+
},
|
|
2177
|
+
registration: {
|
|
2178
|
+
enabled: true,
|
|
2179
|
+
requireEmailVerification: false,
|
|
2180
|
+
defaultRole: "viewer"
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
},
|
|
2184
|
+
{
|
|
2185
|
+
id: "core-media",
|
|
2186
|
+
name: "core-media",
|
|
2187
|
+
display_name: "Media Manager",
|
|
2188
|
+
description: "Core media upload and management system",
|
|
2189
|
+
version: "1.0.0",
|
|
2190
|
+
author: "SonicJS Team",
|
|
2191
|
+
category: "media",
|
|
2192
|
+
icon: "\u{1F4F8}",
|
|
2193
|
+
permissions: ["manage:media", "upload:files"],
|
|
2194
|
+
dependencies: [],
|
|
2195
|
+
settings: {}
|
|
2196
|
+
},
|
|
2197
|
+
{
|
|
2198
|
+
id: "database-tools",
|
|
2199
|
+
name: "database-tools",
|
|
2200
|
+
display_name: "Database Tools",
|
|
2201
|
+
description: "Database management tools including truncate, backup, and validation",
|
|
2202
|
+
version: "1.0.0",
|
|
2203
|
+
author: "SonicJS Team",
|
|
2204
|
+
category: "system",
|
|
2205
|
+
icon: "\u{1F5C4}\uFE0F",
|
|
2206
|
+
permissions: ["manage:database", "admin"],
|
|
2207
|
+
dependencies: [],
|
|
2208
|
+
settings: {
|
|
2209
|
+
enableTruncate: true,
|
|
2210
|
+
enableBackup: true,
|
|
2211
|
+
enableValidation: true,
|
|
2212
|
+
requireConfirmation: true
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
{
|
|
2216
|
+
id: "seed-data",
|
|
2217
|
+
name: "seed-data",
|
|
2218
|
+
display_name: "Seed Data",
|
|
2219
|
+
description: "Generate realistic example users and content for testing and development",
|
|
2220
|
+
version: "1.0.0",
|
|
2221
|
+
author: "SonicJS Team",
|
|
2222
|
+
category: "development",
|
|
2223
|
+
icon: "\u{1F331}",
|
|
2224
|
+
permissions: ["admin"],
|
|
2225
|
+
dependencies: [],
|
|
2226
|
+
settings: {
|
|
2227
|
+
userCount: 20,
|
|
2228
|
+
contentCount: 200,
|
|
2229
|
+
defaultPassword: "password123"
|
|
2230
|
+
}
|
|
2231
|
+
},
|
|
2232
|
+
{
|
|
2233
|
+
id: "core-cache",
|
|
2234
|
+
name: "core-cache",
|
|
2235
|
+
display_name: "Cache System",
|
|
2236
|
+
description: "Three-tiered caching system with memory, KV, and database layers",
|
|
2237
|
+
version: "1.0.0",
|
|
2238
|
+
author: "SonicJS Team",
|
|
2239
|
+
category: "performance",
|
|
2240
|
+
icon: "\u26A1",
|
|
2241
|
+
permissions: ["manage:cache", "view:stats"],
|
|
2242
|
+
dependencies: [],
|
|
2243
|
+
settings: {
|
|
2244
|
+
enableMemoryCache: true,
|
|
2245
|
+
enableKVCache: true,
|
|
2246
|
+
enableDatabaseCache: true,
|
|
2247
|
+
defaultTTL: 3600
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
];
|
|
2251
|
+
/**
|
|
2252
|
+
* Bootstrap all core plugins - install them if they don't exist
|
|
2253
|
+
*/
|
|
2254
|
+
async bootstrapCorePlugins() {
|
|
2255
|
+
console.log("[PluginBootstrap] Starting core plugin bootstrap process...");
|
|
2256
|
+
try {
|
|
2257
|
+
for (const corePlugin of this.CORE_PLUGINS) {
|
|
2258
|
+
await this.ensurePluginInstalled(corePlugin);
|
|
2259
|
+
}
|
|
2260
|
+
console.log(
|
|
2261
|
+
"[PluginBootstrap] Core plugin bootstrap completed successfully"
|
|
2262
|
+
);
|
|
2263
|
+
} catch (error) {
|
|
2264
|
+
console.error("[PluginBootstrap] Error during plugin bootstrap:", error);
|
|
2265
|
+
throw error;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Ensure a specific plugin is installed
|
|
2270
|
+
*/
|
|
2271
|
+
async ensurePluginInstalled(plugin) {
|
|
2272
|
+
try {
|
|
2273
|
+
const existingPlugin = await this.pluginService.getPlugin(plugin.id);
|
|
2274
|
+
if (existingPlugin) {
|
|
2275
|
+
console.log(
|
|
2276
|
+
`[PluginBootstrap] Plugin already installed: ${plugin.display_name} (status: ${existingPlugin.status})`
|
|
2277
|
+
);
|
|
2278
|
+
if (existingPlugin.version !== plugin.version) {
|
|
2279
|
+
console.log(
|
|
2280
|
+
`[PluginBootstrap] Updating plugin version: ${plugin.display_name} from ${existingPlugin.version} to ${plugin.version}`
|
|
2281
|
+
);
|
|
2282
|
+
await this.updatePlugin(plugin);
|
|
2283
|
+
}
|
|
2284
|
+
if (plugin.id === "core-auth" && existingPlugin.status !== "active") {
|
|
2285
|
+
console.log(
|
|
2286
|
+
`[PluginBootstrap] Core-auth plugin is inactive, activating it now...`
|
|
2287
|
+
);
|
|
2288
|
+
await this.pluginService.activatePlugin(plugin.id);
|
|
2289
|
+
}
|
|
2290
|
+
} else {
|
|
2291
|
+
console.log(
|
|
2292
|
+
`[PluginBootstrap] Installing plugin: ${plugin.display_name}`
|
|
2293
|
+
);
|
|
2294
|
+
await this.pluginService.installPlugin({
|
|
2295
|
+
...plugin,
|
|
2296
|
+
is_core: plugin.name.startsWith("core-")
|
|
2297
|
+
});
|
|
2298
|
+
if (plugin.name.startsWith("core-")) {
|
|
2299
|
+
console.log(
|
|
2300
|
+
`[PluginBootstrap] Activating newly installed core plugin: ${plugin.display_name}`
|
|
2301
|
+
);
|
|
2302
|
+
await this.pluginService.activatePlugin(plugin.id);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
} catch (error) {
|
|
2306
|
+
console.error(
|
|
2307
|
+
`[PluginBootstrap] Error ensuring plugin ${plugin.display_name}:`,
|
|
2308
|
+
error
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Update an existing plugin
|
|
2314
|
+
*/
|
|
2315
|
+
async updatePlugin(plugin) {
|
|
2316
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2317
|
+
const stmt = this.db.prepare(`
|
|
2318
|
+
UPDATE plugins
|
|
2319
|
+
SET
|
|
2320
|
+
version = ?,
|
|
2321
|
+
description = ?,
|
|
2322
|
+
permissions = ?,
|
|
2323
|
+
settings = ?,
|
|
2324
|
+
last_updated = ?
|
|
2325
|
+
WHERE id = ?
|
|
2326
|
+
`);
|
|
2327
|
+
await stmt.bind(
|
|
2328
|
+
plugin.version,
|
|
2329
|
+
plugin.description,
|
|
2330
|
+
JSON.stringify(plugin.permissions),
|
|
2331
|
+
JSON.stringify(plugin.settings || {}),
|
|
2332
|
+
now,
|
|
2333
|
+
plugin.id
|
|
2334
|
+
).run();
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Check if bootstrap is needed (first run detection)
|
|
2338
|
+
*/
|
|
2339
|
+
async isBootstrapNeeded() {
|
|
2340
|
+
try {
|
|
2341
|
+
for (const corePlugin of this.CORE_PLUGINS.filter(
|
|
2342
|
+
(p) => p.name.startsWith("core-")
|
|
2343
|
+
)) {
|
|
2344
|
+
const exists = await this.pluginService.getPlugin(corePlugin.id);
|
|
2345
|
+
if (!exists) {
|
|
2346
|
+
return true;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
return false;
|
|
2350
|
+
} catch (error) {
|
|
2351
|
+
console.error(
|
|
2352
|
+
"[PluginBootstrap] Error checking bootstrap status:",
|
|
2353
|
+
error
|
|
2354
|
+
);
|
|
2355
|
+
return true;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
|
|
2360
|
+
exports.Logger = Logger;
|
|
2361
|
+
exports.MigrationService = MigrationService;
|
|
2362
|
+
exports.PluginBootstrapService = PluginBootstrapService;
|
|
2363
|
+
exports.PluginService = PluginService;
|
|
2364
|
+
exports.apiTokens = apiTokens;
|
|
2365
|
+
exports.cleanupRemovedCollections = cleanupRemovedCollections;
|
|
2366
|
+
exports.collections = collections;
|
|
2367
|
+
exports.content = content;
|
|
2368
|
+
exports.contentVersions = contentVersions;
|
|
2369
|
+
exports.fullCollectionSync = fullCollectionSync;
|
|
2370
|
+
exports.getAvailableCollectionNames = getAvailableCollectionNames;
|
|
2371
|
+
exports.getLogger = getLogger;
|
|
2372
|
+
exports.getManagedCollections = getManagedCollections;
|
|
2373
|
+
exports.initLogger = initLogger;
|
|
2374
|
+
exports.insertCollectionSchema = insertCollectionSchema;
|
|
2375
|
+
exports.insertContentSchema = insertContentSchema;
|
|
2376
|
+
exports.insertLogConfigSchema = insertLogConfigSchema;
|
|
2377
|
+
exports.insertMediaSchema = insertMediaSchema;
|
|
2378
|
+
exports.insertPluginActivityLogSchema = insertPluginActivityLogSchema;
|
|
2379
|
+
exports.insertPluginAssetSchema = insertPluginAssetSchema;
|
|
2380
|
+
exports.insertPluginHookSchema = insertPluginHookSchema;
|
|
2381
|
+
exports.insertPluginRouteSchema = insertPluginRouteSchema;
|
|
2382
|
+
exports.insertPluginSchema = insertPluginSchema;
|
|
2383
|
+
exports.insertSystemLogSchema = insertSystemLogSchema;
|
|
2384
|
+
exports.insertUserSchema = insertUserSchema;
|
|
2385
|
+
exports.insertWorkflowHistorySchema = insertWorkflowHistorySchema;
|
|
2386
|
+
exports.isCollectionManaged = isCollectionManaged;
|
|
2387
|
+
exports.loadCollectionConfig = loadCollectionConfig;
|
|
2388
|
+
exports.loadCollectionConfigs = loadCollectionConfigs;
|
|
2389
|
+
exports.logConfig = logConfig;
|
|
2390
|
+
exports.media = media;
|
|
2391
|
+
exports.pluginActivityLog = pluginActivityLog;
|
|
2392
|
+
exports.pluginAssets = pluginAssets;
|
|
2393
|
+
exports.pluginHooks = pluginHooks;
|
|
2394
|
+
exports.pluginRoutes = pluginRoutes;
|
|
2395
|
+
exports.plugins = plugins;
|
|
2396
|
+
exports.schema_exports = schema_exports;
|
|
2397
|
+
exports.selectCollectionSchema = selectCollectionSchema;
|
|
2398
|
+
exports.selectContentSchema = selectContentSchema;
|
|
2399
|
+
exports.selectLogConfigSchema = selectLogConfigSchema;
|
|
2400
|
+
exports.selectMediaSchema = selectMediaSchema;
|
|
2401
|
+
exports.selectPluginActivityLogSchema = selectPluginActivityLogSchema;
|
|
2402
|
+
exports.selectPluginAssetSchema = selectPluginAssetSchema;
|
|
2403
|
+
exports.selectPluginHookSchema = selectPluginHookSchema;
|
|
2404
|
+
exports.selectPluginRouteSchema = selectPluginRouteSchema;
|
|
2405
|
+
exports.selectPluginSchema = selectPluginSchema;
|
|
2406
|
+
exports.selectSystemLogSchema = selectSystemLogSchema;
|
|
2407
|
+
exports.selectUserSchema = selectUserSchema;
|
|
2408
|
+
exports.selectWorkflowHistorySchema = selectWorkflowHistorySchema;
|
|
2409
|
+
exports.syncCollection = syncCollection;
|
|
2410
|
+
exports.syncCollections = syncCollections;
|
|
2411
|
+
exports.systemLogs = systemLogs;
|
|
2412
|
+
exports.users = users;
|
|
2413
|
+
exports.validateCollectionConfig = validateCollectionConfig;
|
|
2414
|
+
exports.workflowHistory = workflowHistory;
|
|
2415
|
+
//# sourceMappingURL=chunk-WJ7QYVR2.cjs.map
|
|
2416
|
+
//# sourceMappingURL=chunk-WJ7QYVR2.cjs.map
|