@sonicjs-cms/core 2.0.11 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -1
- package/dist/{chunk-AMSTLQFI.cjs → chunk-22EFGHAX.cjs} +4 -4
- package/dist/chunk-22EFGHAX.cjs.map +1 -0
- package/dist/{chunk-IM2LGCYD.cjs → chunk-2CP6535T.cjs} +905 -565
- package/dist/chunk-2CP6535T.cjs.map +1 -0
- package/dist/{chunk-HKEK7UNV.js → chunk-CPXAVWCU.js} +3 -3
- package/dist/{chunk-HKEK7UNV.js.map → chunk-CPXAVWCU.js.map} +1 -1
- package/dist/{chunk-F5ESJXI2.cjs → chunk-DTLB6UIH.cjs} +3 -3
- package/dist/{chunk-F5ESJXI2.cjs.map → chunk-DTLB6UIH.cjs.map} +1 -1
- package/dist/chunk-HFFNEGZB.cjs +70 -0
- package/dist/chunk-HFFNEGZB.cjs.map +1 -0
- package/dist/chunk-I3R77LQC.js +1550 -0
- package/dist/chunk-I3R77LQC.js.map +1 -0
- package/dist/chunk-JQIQRMPA.cjs +1552 -0
- package/dist/chunk-JQIQRMPA.cjs.map +1 -0
- package/dist/{chunk-I5ZPYKNX.js → chunk-LWMMMW43.js} +4 -4
- package/dist/chunk-LWMMMW43.js.map +1 -0
- package/dist/chunk-MOWI4WYE.js +61 -0
- package/dist/chunk-MOWI4WYE.js.map +1 -0
- package/dist/{chunk-FYWJMETG.js → chunk-PFNUOW6W.js} +4 -4
- package/dist/{chunk-FYWJMETG.js.map → chunk-PFNUOW6W.js.map} +1 -1
- package/dist/{chunk-CLLJFZ5U.js → chunk-QWIXOMHW.js} +805 -466
- package/dist/chunk-QWIXOMHW.js.map +1 -0
- package/dist/{chunk-DOR2IU73.cjs → chunk-U2WYUO32.cjs} +249 -2
- package/dist/chunk-U2WYUO32.cjs.map +1 -0
- package/dist/{chunk-NNXPAPUD.cjs → chunk-WFHLBYNA.cjs} +7 -4
- package/dist/chunk-WFHLBYNA.cjs.map +1 -0
- package/dist/{chunk-X2VADBA4.cjs → chunk-WML2ZMHH.cjs} +7 -7
- package/dist/{chunk-X2VADBA4.cjs.map → chunk-WML2ZMHH.cjs.map} +1 -1
- package/dist/{chunk-DU7JJZN7.js → chunk-ZALNKBLS.js} +7 -4
- package/dist/chunk-ZALNKBLS.js.map +1 -0
- package/dist/{chunk-6FR25MPC.js → chunk-ZNESOOF2.js} +246 -3
- package/dist/chunk-ZNESOOF2.js.map +1 -0
- package/dist/index.cjs +139 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +24 -24
- package/dist/middleware.js +3 -3
- package/dist/migrations-GVG73ZJC.js +4 -0
- package/dist/{migrations-IHERIQVD.js.map → migrations-GVG73ZJC.js.map} +1 -1
- package/dist/migrations-S52MEE4J.cjs +13 -0
- package/dist/{migrations-POFD5KNG.cjs.map → migrations-S52MEE4J.cjs.map} +1 -1
- package/dist/plugins.cjs +7 -7
- package/dist/plugins.js +1 -1
- package/dist/routes.cjs +30 -25
- package/dist/routes.js +7 -6
- package/dist/services.cjs +41 -24
- package/dist/services.js +4 -3
- package/dist/utils.cjs +44 -11
- package/dist/utils.js +2 -1
- package/migrations/001_initial_schema.sql +1 -1
- package/migrations/{023_add_mdxeditor_plugin.sql → 023_add_easy_mdx_plugin.sql} +7 -7
- package/migrations/025_rename_mdxeditor_to_easy_mdx.sql +22 -0
- package/package.json +5 -2
- package/dist/chunk-6FR25MPC.js.map +0 -1
- package/dist/chunk-AMSTLQFI.cjs.map +0 -1
- package/dist/chunk-CLLJFZ5U.js.map +0 -1
- package/dist/chunk-DOR2IU73.cjs.map +0 -1
- package/dist/chunk-DU7JJZN7.js.map +0 -1
- package/dist/chunk-I5ZPYKNX.js.map +0 -1
- package/dist/chunk-IM2LGCYD.cjs.map +0 -1
- package/dist/chunk-NNXPAPUD.cjs.map +0 -1
- package/dist/chunk-T7IYBGGO.cjs +0 -746
- package/dist/chunk-T7IYBGGO.cjs.map +0 -1
- package/dist/chunk-ZPMFT2JW.js +0 -744
- package/dist/chunk-ZPMFT2JW.js.map +0 -1
- package/dist/migrations-IHERIQVD.js +0 -4
- package/dist/migrations-POFD5KNG.cjs +0 -13
- package/migrations/013_code_examples_plugin.sql +0 -177
- package/migrations/025_add_easymde_plugin.sql +0 -25
- /package/migrations/{021_add_otp_login.sql → 026_add_otp_login.sql} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-
|
|
2
|
-
import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-
|
|
3
|
-
import { PluginService } from './chunk-
|
|
4
|
-
import { MigrationService } from './chunk-
|
|
1
|
+
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-ZNESOOF2.js';
|
|
2
|
+
import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-PFNUOW6W.js';
|
|
3
|
+
import { PluginService } from './chunk-LWMMMW43.js';
|
|
4
|
+
import { MigrationService } from './chunk-I3R77LQC.js';
|
|
5
5
|
import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-5RKQB2JG.js';
|
|
6
|
-
import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-
|
|
6
|
+
import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-ZALNKBLS.js';
|
|
7
7
|
import { metricsTracker } from './chunk-FICTAGD4.js';
|
|
8
8
|
import { Hono } from 'hono';
|
|
9
9
|
import { cors } from 'hono/cors';
|
|
@@ -17,8 +17,8 @@ var apiContentCrudRoutes = new Hono();
|
|
|
17
17
|
apiContentCrudRoutes.get("/:id", async (c) => {
|
|
18
18
|
try {
|
|
19
19
|
const id = c.req.param("id");
|
|
20
|
-
const
|
|
21
|
-
const stmt =
|
|
20
|
+
const db2 = c.env.DB;
|
|
21
|
+
const stmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
22
22
|
const content = await stmt.bind(id).first();
|
|
23
23
|
if (!content) {
|
|
24
24
|
return c.json({ error: "Content not found" }, 404);
|
|
@@ -44,7 +44,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
|
|
|
44
44
|
});
|
|
45
45
|
apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
46
46
|
try {
|
|
47
|
-
const
|
|
47
|
+
const db2 = c.env.DB;
|
|
48
48
|
const user = c.get("user");
|
|
49
49
|
const body = await c.req.json();
|
|
50
50
|
const { collectionId, title, slug, status, data } = body;
|
|
@@ -56,7 +56,7 @@ apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
|
56
56
|
}
|
|
57
57
|
let finalSlug = slug || title;
|
|
58
58
|
finalSlug = finalSlug.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").trim();
|
|
59
|
-
const duplicateCheck =
|
|
59
|
+
const duplicateCheck = db2.prepare(
|
|
60
60
|
"SELECT id FROM content WHERE collection_id = ? AND slug = ?"
|
|
61
61
|
);
|
|
62
62
|
const existing = await duplicateCheck.bind(collectionId, finalSlug).first();
|
|
@@ -65,7 +65,7 @@ apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
|
65
65
|
}
|
|
66
66
|
const contentId = crypto.randomUUID();
|
|
67
67
|
const now = Date.now();
|
|
68
|
-
const insertStmt =
|
|
68
|
+
const insertStmt = db2.prepare(`
|
|
69
69
|
INSERT INTO content (
|
|
70
70
|
id, collection_id, slug, title, data, status,
|
|
71
71
|
author_id, created_at, updated_at
|
|
@@ -86,7 +86,7 @@ apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
|
86
86
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
87
87
|
await cache.invalidate(`content:list:${collectionId}:*`);
|
|
88
88
|
await cache.invalidate("content-filtered:*");
|
|
89
|
-
const getStmt =
|
|
89
|
+
const getStmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
90
90
|
const createdContent = await getStmt.bind(contentId).first();
|
|
91
91
|
return c.json({
|
|
92
92
|
data: {
|
|
@@ -111,9 +111,9 @@ apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
|
111
111
|
apiContentCrudRoutes.put("/:id", requireAuth(), async (c) => {
|
|
112
112
|
try {
|
|
113
113
|
const id = c.req.param("id");
|
|
114
|
-
const
|
|
114
|
+
const db2 = c.env.DB;
|
|
115
115
|
const body = await c.req.json();
|
|
116
|
-
const existingStmt =
|
|
116
|
+
const existingStmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
117
117
|
const existing = await existingStmt.bind(id).first();
|
|
118
118
|
if (!existing) {
|
|
119
119
|
return c.json({ error: "Content not found" }, 404);
|
|
@@ -141,7 +141,7 @@ apiContentCrudRoutes.put("/:id", requireAuth(), async (c) => {
|
|
|
141
141
|
updates.push("updated_at = ?");
|
|
142
142
|
params.push(now);
|
|
143
143
|
params.push(id);
|
|
144
|
-
const updateStmt =
|
|
144
|
+
const updateStmt = db2.prepare(`
|
|
145
145
|
UPDATE content SET ${updates.join(", ")}
|
|
146
146
|
WHERE id = ?
|
|
147
147
|
`);
|
|
@@ -150,7 +150,7 @@ apiContentCrudRoutes.put("/:id", requireAuth(), async (c) => {
|
|
|
150
150
|
await cache.delete(cache.generateKey("content", id));
|
|
151
151
|
await cache.invalidate(`content:list:${existing.collection_id}:*`);
|
|
152
152
|
await cache.invalidate("content-filtered:*");
|
|
153
|
-
const getStmt =
|
|
153
|
+
const getStmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
154
154
|
const updatedContent = await getStmt.bind(id).first();
|
|
155
155
|
return c.json({
|
|
156
156
|
data: {
|
|
@@ -175,13 +175,13 @@ apiContentCrudRoutes.put("/:id", requireAuth(), async (c) => {
|
|
|
175
175
|
apiContentCrudRoutes.delete("/:id", requireAuth(), async (c) => {
|
|
176
176
|
try {
|
|
177
177
|
const id = c.req.param("id");
|
|
178
|
-
const
|
|
179
|
-
const existingStmt =
|
|
178
|
+
const db2 = c.env.DB;
|
|
179
|
+
const existingStmt = db2.prepare("SELECT collection_id FROM content WHERE id = ?");
|
|
180
180
|
const existing = await existingStmt.bind(id).first();
|
|
181
181
|
if (!existing) {
|
|
182
182
|
return c.json({ error: "Content not found" }, 404);
|
|
183
183
|
}
|
|
184
|
-
const deleteStmt =
|
|
184
|
+
const deleteStmt = db2.prepare("DELETE FROM content WHERE id = ?");
|
|
185
185
|
await deleteStmt.bind(id).run();
|
|
186
186
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
187
187
|
await cache.delete(cache.generateKey("content", id));
|
|
@@ -254,7 +254,7 @@ apiRoutes.get("/health", (c) => {
|
|
|
254
254
|
apiRoutes.get("/collections", async (c) => {
|
|
255
255
|
const executionStart = Date.now();
|
|
256
256
|
try {
|
|
257
|
-
const
|
|
257
|
+
const db2 = c.env.DB;
|
|
258
258
|
const cacheEnabled = c.get("cacheEnabled");
|
|
259
259
|
const cache = getCacheService(CACHE_CONFIGS.api);
|
|
260
260
|
const cacheKey = cache.generateKey("collections", "all");
|
|
@@ -282,7 +282,7 @@ apiRoutes.get("/collections", async (c) => {
|
|
|
282
282
|
}
|
|
283
283
|
c.header("X-Cache-Status", "MISS");
|
|
284
284
|
c.header("X-Cache-Source", "database");
|
|
285
|
-
const stmt =
|
|
285
|
+
const stmt = db2.prepare("SELECT * FROM collections WHERE is_active = 1");
|
|
286
286
|
const { results } = await stmt.all();
|
|
287
287
|
const transformedResults = results.map((row) => ({
|
|
288
288
|
...row,
|
|
@@ -313,11 +313,11 @@ apiRoutes.get("/collections", async (c) => {
|
|
|
313
313
|
apiRoutes.get("/content", async (c) => {
|
|
314
314
|
const executionStart = Date.now();
|
|
315
315
|
try {
|
|
316
|
-
const
|
|
316
|
+
const db2 = c.env.DB;
|
|
317
317
|
const queryParams = c.req.query();
|
|
318
318
|
if (queryParams.collection) {
|
|
319
319
|
const collectionName = queryParams.collection;
|
|
320
|
-
const collectionStmt =
|
|
320
|
+
const collectionStmt = db2.prepare("SELECT id FROM collections WHERE name = ? AND is_active = 1");
|
|
321
321
|
const collectionResult = await collectionStmt.bind(collectionName).first();
|
|
322
322
|
if (collectionResult) {
|
|
323
323
|
queryParams.collection_id = collectionResult.id;
|
|
@@ -373,7 +373,7 @@ apiRoutes.get("/content", async (c) => {
|
|
|
373
373
|
}
|
|
374
374
|
c.header("X-Cache-Status", "MISS");
|
|
375
375
|
c.header("X-Cache-Source", "database");
|
|
376
|
-
const stmt =
|
|
376
|
+
const stmt = db2.prepare(queryResult.sql);
|
|
377
377
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
378
378
|
const { results } = await boundStmt.all();
|
|
379
379
|
const transformedResults = results.map((row) => ({
|
|
@@ -418,9 +418,9 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
|
|
|
418
418
|
const executionStart = Date.now();
|
|
419
419
|
try {
|
|
420
420
|
const collection = c.req.param("collection");
|
|
421
|
-
const
|
|
421
|
+
const db2 = c.env.DB;
|
|
422
422
|
const queryParams = c.req.query();
|
|
423
|
-
const collectionStmt =
|
|
423
|
+
const collectionStmt = db2.prepare("SELECT * FROM collections WHERE name = ? AND is_active = 1");
|
|
424
424
|
const collectionResult = await collectionStmt.bind(collection).first();
|
|
425
425
|
if (!collectionResult) {
|
|
426
426
|
return c.json({ error: "Collection not found" }, 404);
|
|
@@ -476,7 +476,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
|
|
|
476
476
|
}
|
|
477
477
|
c.header("X-Cache-Status", "MISS");
|
|
478
478
|
c.header("X-Cache-Source", "database");
|
|
479
|
-
const stmt =
|
|
479
|
+
const stmt = db2.prepare(queryResult.sql);
|
|
480
480
|
const boundStmt = queryResult.params.length > 0 ? stmt.bind(...queryResult.params) : stmt;
|
|
481
481
|
const { results } = await boundStmt.all();
|
|
482
482
|
const transformedResults = results.map((row) => ({
|
|
@@ -1240,20 +1240,20 @@ apiSystemRoutes.get("/info", (c) => {
|
|
|
1240
1240
|
});
|
|
1241
1241
|
apiSystemRoutes.get("/stats", async (c) => {
|
|
1242
1242
|
try {
|
|
1243
|
-
const
|
|
1244
|
-
const contentStats = await
|
|
1243
|
+
const db2 = c.env.DB;
|
|
1244
|
+
const contentStats = await db2.prepare(`
|
|
1245
1245
|
SELECT COUNT(*) as total_content
|
|
1246
1246
|
FROM content
|
|
1247
1247
|
WHERE deleted_at IS NULL
|
|
1248
1248
|
`).first();
|
|
1249
|
-
const mediaStats = await
|
|
1249
|
+
const mediaStats = await db2.prepare(`
|
|
1250
1250
|
SELECT
|
|
1251
1251
|
COUNT(*) as total_files,
|
|
1252
1252
|
SUM(size) as total_size
|
|
1253
1253
|
FROM media
|
|
1254
1254
|
WHERE deleted_at IS NULL
|
|
1255
1255
|
`).first();
|
|
1256
|
-
const userStats = await
|
|
1256
|
+
const userStats = await db2.prepare(`
|
|
1257
1257
|
SELECT COUNT(*) as total_users
|
|
1258
1258
|
FROM users
|
|
1259
1259
|
`).first();
|
|
@@ -1314,10 +1314,10 @@ adminApiRoutes.use("*", requireAuth());
|
|
|
1314
1314
|
adminApiRoutes.use("*", requireRole(["admin", "editor"]));
|
|
1315
1315
|
adminApiRoutes.get("/stats", async (c) => {
|
|
1316
1316
|
try {
|
|
1317
|
-
const
|
|
1317
|
+
const db2 = c.env.DB;
|
|
1318
1318
|
let collectionsCount = 0;
|
|
1319
1319
|
try {
|
|
1320
|
-
const collectionsStmt =
|
|
1320
|
+
const collectionsStmt = db2.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1");
|
|
1321
1321
|
const collectionsResult = await collectionsStmt.first();
|
|
1322
1322
|
collectionsCount = collectionsResult?.count || 0;
|
|
1323
1323
|
} catch (error) {
|
|
@@ -1325,7 +1325,7 @@ adminApiRoutes.get("/stats", async (c) => {
|
|
|
1325
1325
|
}
|
|
1326
1326
|
let contentCount = 0;
|
|
1327
1327
|
try {
|
|
1328
|
-
const contentStmt =
|
|
1328
|
+
const contentStmt = db2.prepare("SELECT COUNT(*) as count FROM content WHERE deleted_at IS NULL");
|
|
1329
1329
|
const contentResult = await contentStmt.first();
|
|
1330
1330
|
contentCount = contentResult?.count || 0;
|
|
1331
1331
|
} catch (error) {
|
|
@@ -1334,7 +1334,7 @@ adminApiRoutes.get("/stats", async (c) => {
|
|
|
1334
1334
|
let mediaCount = 0;
|
|
1335
1335
|
let mediaSize = 0;
|
|
1336
1336
|
try {
|
|
1337
|
-
const mediaStmt =
|
|
1337
|
+
const mediaStmt = db2.prepare("SELECT COUNT(*) as count, COALESCE(SUM(size), 0) as total_size FROM media WHERE deleted_at IS NULL");
|
|
1338
1338
|
const mediaResult = await mediaStmt.first();
|
|
1339
1339
|
mediaCount = mediaResult?.count || 0;
|
|
1340
1340
|
mediaSize = mediaResult?.total_size || 0;
|
|
@@ -1343,7 +1343,7 @@ adminApiRoutes.get("/stats", async (c) => {
|
|
|
1343
1343
|
}
|
|
1344
1344
|
let usersCount = 0;
|
|
1345
1345
|
try {
|
|
1346
|
-
const usersStmt =
|
|
1346
|
+
const usersStmt = db2.prepare("SELECT COUNT(*) as count FROM users WHERE is_active = 1");
|
|
1347
1347
|
const usersResult = await usersStmt.first();
|
|
1348
1348
|
usersCount = usersResult?.count || 0;
|
|
1349
1349
|
} catch (error) {
|
|
@@ -1364,17 +1364,17 @@ adminApiRoutes.get("/stats", async (c) => {
|
|
|
1364
1364
|
});
|
|
1365
1365
|
adminApiRoutes.get("/storage", async (c) => {
|
|
1366
1366
|
try {
|
|
1367
|
-
const
|
|
1367
|
+
const db2 = c.env.DB;
|
|
1368
1368
|
let databaseSize = 0;
|
|
1369
1369
|
try {
|
|
1370
|
-
const result = await
|
|
1370
|
+
const result = await db2.prepare("SELECT 1").run();
|
|
1371
1371
|
databaseSize = result?.meta?.size_after || 0;
|
|
1372
1372
|
} catch (error) {
|
|
1373
1373
|
console.error("Error fetching database size:", error);
|
|
1374
1374
|
}
|
|
1375
1375
|
let mediaSize = 0;
|
|
1376
1376
|
try {
|
|
1377
|
-
const mediaStmt =
|
|
1377
|
+
const mediaStmt = db2.prepare("SELECT COALESCE(SUM(size), 0) as total_size FROM media WHERE deleted_at IS NULL");
|
|
1378
1378
|
const mediaResult = await mediaStmt.first();
|
|
1379
1379
|
mediaSize = mediaResult?.total_size || 0;
|
|
1380
1380
|
} catch (error) {
|
|
@@ -1393,9 +1393,9 @@ adminApiRoutes.get("/storage", async (c) => {
|
|
|
1393
1393
|
});
|
|
1394
1394
|
adminApiRoutes.get("/activity", async (c) => {
|
|
1395
1395
|
try {
|
|
1396
|
-
const
|
|
1396
|
+
const db2 = c.env.DB;
|
|
1397
1397
|
const limit = parseInt(c.req.query("limit") || "10");
|
|
1398
|
-
const activityStmt =
|
|
1398
|
+
const activityStmt = db2.prepare(`
|
|
1399
1399
|
SELECT
|
|
1400
1400
|
a.id,
|
|
1401
1401
|
a.action,
|
|
@@ -1457,13 +1457,13 @@ var updateCollectionSchema = z.object({
|
|
|
1457
1457
|
});
|
|
1458
1458
|
adminApiRoutes.get("/collections", async (c) => {
|
|
1459
1459
|
try {
|
|
1460
|
-
const
|
|
1460
|
+
const db2 = c.env.DB;
|
|
1461
1461
|
const search = c.req.query("search") || "";
|
|
1462
1462
|
const includeInactive = c.req.query("includeInactive") === "true";
|
|
1463
1463
|
let stmt;
|
|
1464
1464
|
let results;
|
|
1465
1465
|
if (search) {
|
|
1466
|
-
stmt =
|
|
1466
|
+
stmt = db2.prepare(`
|
|
1467
1467
|
SELECT id, name, display_name, description, created_at, updated_at, is_active, managed
|
|
1468
1468
|
FROM collections
|
|
1469
1469
|
WHERE ${includeInactive ? "1=1" : "is_active = 1"}
|
|
@@ -1474,7 +1474,7 @@ adminApiRoutes.get("/collections", async (c) => {
|
|
|
1474
1474
|
const queryResults = await stmt.bind(searchParam, searchParam, searchParam).all();
|
|
1475
1475
|
results = queryResults.results;
|
|
1476
1476
|
} else {
|
|
1477
|
-
stmt =
|
|
1477
|
+
stmt = db2.prepare(`
|
|
1478
1478
|
SELECT id, name, display_name, description, created_at, updated_at, is_active, managed
|
|
1479
1479
|
FROM collections
|
|
1480
1480
|
${includeInactive ? "" : "WHERE is_active = 1"}
|
|
@@ -1483,7 +1483,7 @@ adminApiRoutes.get("/collections", async (c) => {
|
|
|
1483
1483
|
const queryResults = await stmt.all();
|
|
1484
1484
|
results = queryResults.results;
|
|
1485
1485
|
}
|
|
1486
|
-
const fieldCountStmt =
|
|
1486
|
+
const fieldCountStmt = db2.prepare("SELECT collection_id, COUNT(*) as count FROM content_fields GROUP BY collection_id");
|
|
1487
1487
|
const { results: fieldCountResults } = await fieldCountStmt.all();
|
|
1488
1488
|
const fieldCounts = new Map((fieldCountResults || []).map((row) => [String(row.collection_id), Number(row.count)]));
|
|
1489
1489
|
const collections = (results || []).map((row) => ({
|
|
@@ -1510,13 +1510,13 @@ adminApiRoutes.get("/collections", async (c) => {
|
|
|
1510
1510
|
adminApiRoutes.get("/collections/:id", async (c) => {
|
|
1511
1511
|
try {
|
|
1512
1512
|
const id = c.req.param("id");
|
|
1513
|
-
const
|
|
1514
|
-
const stmt =
|
|
1513
|
+
const db2 = c.env.DB;
|
|
1514
|
+
const stmt = db2.prepare("SELECT * FROM collections WHERE id = ?");
|
|
1515
1515
|
const collection = await stmt.bind(id).first();
|
|
1516
1516
|
if (!collection) {
|
|
1517
1517
|
return c.json({ error: "Collection not found" }, 404);
|
|
1518
1518
|
}
|
|
1519
|
-
const fieldsStmt =
|
|
1519
|
+
const fieldsStmt = db2.prepare(`
|
|
1520
1520
|
SELECT * FROM content_fields
|
|
1521
1521
|
WHERE collection_id = ?
|
|
1522
1522
|
ORDER BY field_order ASC
|
|
@@ -1565,13 +1565,13 @@ adminApiRoutes.post("/collections", async (c) => {
|
|
|
1565
1565
|
}
|
|
1566
1566
|
const validation = createCollectionSchema.safeParse(body);
|
|
1567
1567
|
if (!validation.success) {
|
|
1568
|
-
return c.json({ error: "Validation failed", details: validation.error.
|
|
1568
|
+
return c.json({ error: "Validation failed", details: validation.error.issues }, 400);
|
|
1569
1569
|
}
|
|
1570
1570
|
const validatedData = validation.data;
|
|
1571
|
-
const
|
|
1571
|
+
const db2 = c.env.DB;
|
|
1572
1572
|
const ____user = c.get("user");
|
|
1573
1573
|
const displayName = validatedData.displayName || validatedData.display_name || "";
|
|
1574
|
-
const existingStmt =
|
|
1574
|
+
const existingStmt = db2.prepare("SELECT id FROM collections WHERE name = ?");
|
|
1575
1575
|
const existing = await existingStmt.bind(validatedData.name).first();
|
|
1576
1576
|
if (existing) {
|
|
1577
1577
|
return c.json({ error: "A collection with this name already exists" }, 400);
|
|
@@ -1600,7 +1600,7 @@ adminApiRoutes.post("/collections", async (c) => {
|
|
|
1600
1600
|
};
|
|
1601
1601
|
const collectionId = crypto.randomUUID();
|
|
1602
1602
|
const now = Date.now();
|
|
1603
|
-
const insertStmt =
|
|
1603
|
+
const insertStmt = db2.prepare(`
|
|
1604
1604
|
INSERT INTO collections (id, name, display_name, description, schema, is_active, created_at, updated_at)
|
|
1605
1605
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1606
1606
|
`);
|
|
@@ -1639,11 +1639,11 @@ adminApiRoutes.patch("/collections/:id", async (c) => {
|
|
|
1639
1639
|
const body = await c.req.json();
|
|
1640
1640
|
const validation = updateCollectionSchema.safeParse(body);
|
|
1641
1641
|
if (!validation.success) {
|
|
1642
|
-
return c.json({ error: "Validation failed", details: validation.error.
|
|
1642
|
+
return c.json({ error: "Validation failed", details: validation.error.issues }, 400);
|
|
1643
1643
|
}
|
|
1644
1644
|
const validatedData = validation.data;
|
|
1645
|
-
const
|
|
1646
|
-
const checkStmt =
|
|
1645
|
+
const db2 = c.env.DB;
|
|
1646
|
+
const checkStmt = db2.prepare("SELECT * FROM collections WHERE id = ?");
|
|
1647
1647
|
const existing = await checkStmt.bind(id).first();
|
|
1648
1648
|
if (!existing) {
|
|
1649
1649
|
return c.json({ error: "Collection not found" }, 404);
|
|
@@ -1668,7 +1668,7 @@ adminApiRoutes.patch("/collections/:id", async (c) => {
|
|
|
1668
1668
|
updateFields.push("updated_at = ?");
|
|
1669
1669
|
updateParams.push(Date.now());
|
|
1670
1670
|
updateParams.push(id);
|
|
1671
|
-
const updateStmt =
|
|
1671
|
+
const updateStmt = db2.prepare(`
|
|
1672
1672
|
UPDATE collections
|
|
1673
1673
|
SET ${updateFields.join(", ")}
|
|
1674
1674
|
WHERE id = ?
|
|
@@ -1689,22 +1689,22 @@ adminApiRoutes.patch("/collections/:id", async (c) => {
|
|
|
1689
1689
|
adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
1690
1690
|
try {
|
|
1691
1691
|
const id = c.req.param("id");
|
|
1692
|
-
const
|
|
1693
|
-
const collectionStmt =
|
|
1692
|
+
const db2 = c.env.DB;
|
|
1693
|
+
const collectionStmt = db2.prepare("SELECT name FROM collections WHERE id = ?");
|
|
1694
1694
|
const collection = await collectionStmt.bind(id).first();
|
|
1695
1695
|
if (!collection) {
|
|
1696
1696
|
return c.json({ error: "Collection not found" }, 404);
|
|
1697
1697
|
}
|
|
1698
|
-
const contentStmt =
|
|
1698
|
+
const contentStmt = db2.prepare("SELECT COUNT(*) as count FROM content WHERE collection_id = ?");
|
|
1699
1699
|
const contentResult = await contentStmt.bind(id).first();
|
|
1700
1700
|
if (contentResult && contentResult.count > 0) {
|
|
1701
1701
|
return c.json({
|
|
1702
1702
|
error: `Cannot delete collection: it contains ${contentResult.count} content item(s). Delete all content first.`
|
|
1703
1703
|
}, 400);
|
|
1704
1704
|
}
|
|
1705
|
-
const deleteFieldsStmt =
|
|
1705
|
+
const deleteFieldsStmt = db2.prepare("DELETE FROM content_fields WHERE collection_id = ?");
|
|
1706
1706
|
await deleteFieldsStmt.bind(id).run();
|
|
1707
|
-
const deleteStmt =
|
|
1707
|
+
const deleteStmt = db2.prepare("DELETE FROM collections WHERE id = ?");
|
|
1708
1708
|
await deleteStmt.bind(id).run();
|
|
1709
1709
|
try {
|
|
1710
1710
|
await c.env.CACHE_KV.delete("cache:collections:all");
|
|
@@ -1720,9 +1720,9 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
1720
1720
|
});
|
|
1721
1721
|
adminApiRoutes.get("/migrations/status", async (c) => {
|
|
1722
1722
|
try {
|
|
1723
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1724
|
-
const
|
|
1725
|
-
const migrationService = new MigrationService2(
|
|
1723
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-GVG73ZJC.js');
|
|
1724
|
+
const db2 = c.env.DB;
|
|
1725
|
+
const migrationService = new MigrationService2(db2);
|
|
1726
1726
|
const status = await migrationService.getMigrationStatus();
|
|
1727
1727
|
return c.json({
|
|
1728
1728
|
success: true,
|
|
@@ -1745,9 +1745,9 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1745
1745
|
error: "Unauthorized. Admin access required."
|
|
1746
1746
|
}, 403);
|
|
1747
1747
|
}
|
|
1748
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1749
|
-
const
|
|
1750
|
-
const migrationService = new MigrationService2(
|
|
1748
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-GVG73ZJC.js');
|
|
1749
|
+
const db2 = c.env.DB;
|
|
1750
|
+
const migrationService = new MigrationService2(db2);
|
|
1751
1751
|
const result = await migrationService.runPendingMigrations();
|
|
1752
1752
|
return c.json({
|
|
1753
1753
|
success: result.success,
|
|
@@ -1764,9 +1764,9 @@ adminApiRoutes.post("/migrations/run", async (c) => {
|
|
|
1764
1764
|
});
|
|
1765
1765
|
adminApiRoutes.get("/migrations/validate", async (c) => {
|
|
1766
1766
|
try {
|
|
1767
|
-
const { MigrationService: MigrationService2 } = await import('./migrations-
|
|
1768
|
-
const
|
|
1769
|
-
const migrationService = new MigrationService2(
|
|
1767
|
+
const { MigrationService: MigrationService2 } = await import('./migrations-GVG73ZJC.js');
|
|
1768
|
+
const db2 = c.env.DB;
|
|
1769
|
+
const migrationService = new MigrationService2(db2);
|
|
1770
1770
|
const validation = await migrationService.validateSchema();
|
|
1771
1771
|
return c.json({
|
|
1772
1772
|
success: true,
|
|
@@ -2158,10 +2158,10 @@ authRoutes.get("/login", async (c) => {
|
|
|
2158
2158
|
message: message || void 0,
|
|
2159
2159
|
version: c.get("appVersion")
|
|
2160
2160
|
};
|
|
2161
|
-
const
|
|
2161
|
+
const db2 = c.env.DB;
|
|
2162
2162
|
let demoLoginActive = false;
|
|
2163
2163
|
try {
|
|
2164
|
-
const plugin = await
|
|
2164
|
+
const plugin = await db2.prepare("SELECT * FROM plugins WHERE id = ? AND status = ?").bind("demo-login-prefill", "active").first();
|
|
2165
2165
|
demoLoginActive = !!plugin;
|
|
2166
2166
|
} catch (error2) {
|
|
2167
2167
|
}
|
|
@@ -2182,21 +2182,21 @@ authRoutes.post(
|
|
|
2182
2182
|
"/register",
|
|
2183
2183
|
async (c) => {
|
|
2184
2184
|
try {
|
|
2185
|
-
const
|
|
2185
|
+
const db2 = c.env.DB;
|
|
2186
2186
|
let requestData;
|
|
2187
2187
|
try {
|
|
2188
2188
|
requestData = await c.req.json();
|
|
2189
2189
|
} catch (parseError) {
|
|
2190
2190
|
return c.json({ error: "Invalid JSON in request body" }, 400);
|
|
2191
2191
|
}
|
|
2192
|
-
const validationSchema = await authValidationService.buildRegistrationSchema(
|
|
2192
|
+
const validationSchema = await authValidationService.buildRegistrationSchema(db2);
|
|
2193
2193
|
let validatedData;
|
|
2194
2194
|
try {
|
|
2195
2195
|
validatedData = await validationSchema.parseAsync(requestData);
|
|
2196
2196
|
} catch (validationError) {
|
|
2197
2197
|
return c.json({
|
|
2198
2198
|
error: "Validation failed",
|
|
2199
|
-
details: validationError.
|
|
2199
|
+
details: validationError.issues?.map((e) => e.message) || [validationError.message || "Invalid request data"]
|
|
2200
2200
|
}, 400);
|
|
2201
2201
|
}
|
|
2202
2202
|
const email = validatedData.email;
|
|
@@ -2205,14 +2205,14 @@ authRoutes.post(
|
|
|
2205
2205
|
const firstName = validatedData.firstName || authValidationService.generateDefaultValue("firstName", validatedData);
|
|
2206
2206
|
const lastName = validatedData.lastName || authValidationService.generateDefaultValue("lastName", validatedData);
|
|
2207
2207
|
const normalizedEmail = email.toLowerCase();
|
|
2208
|
-
const existingUser = await
|
|
2208
|
+
const existingUser = await db2.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind(normalizedEmail, username).first();
|
|
2209
2209
|
if (existingUser) {
|
|
2210
2210
|
return c.json({ error: "User with this email or username already exists" }, 400);
|
|
2211
2211
|
}
|
|
2212
2212
|
const passwordHash = await AuthManager.hashPassword(password);
|
|
2213
2213
|
const userId = crypto.randomUUID();
|
|
2214
2214
|
const now = /* @__PURE__ */ new Date();
|
|
2215
|
-
await
|
|
2215
|
+
await db2.prepare(`
|
|
2216
2216
|
INSERT INTO users (id, email, username, first_name, last_name, password_hash, role, is_active, created_at, updated_at)
|
|
2217
2217
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2218
2218
|
`).bind(
|
|
@@ -2265,15 +2265,15 @@ authRoutes.post("/login", async (c) => {
|
|
|
2265
2265
|
const body = await c.req.json();
|
|
2266
2266
|
const validation = loginSchema.safeParse(body);
|
|
2267
2267
|
if (!validation.success) {
|
|
2268
|
-
return c.json({ error: "Validation failed", details: validation.error.
|
|
2268
|
+
return c.json({ error: "Validation failed", details: validation.error.issues }, 400);
|
|
2269
2269
|
}
|
|
2270
2270
|
const { email, password } = validation.data;
|
|
2271
|
-
const
|
|
2271
|
+
const db2 = c.env.DB;
|
|
2272
2272
|
const normalizedEmail = email.toLowerCase();
|
|
2273
2273
|
const cache = getCacheService(CACHE_CONFIGS.user);
|
|
2274
2274
|
let user = await cache.get(cache.generateKey("user", `email:${normalizedEmail}`));
|
|
2275
2275
|
if (!user) {
|
|
2276
|
-
user = await
|
|
2276
|
+
user = await db2.prepare("SELECT * FROM users WHERE email = ? AND is_active = 1").bind(normalizedEmail).first();
|
|
2277
2277
|
if (user) {
|
|
2278
2278
|
await cache.set(cache.generateKey("user", `email:${normalizedEmail}`), user);
|
|
2279
2279
|
await cache.set(cache.generateKey("user", user.id), user);
|
|
@@ -2294,7 +2294,7 @@ authRoutes.post("/login", async (c) => {
|
|
|
2294
2294
|
maxAge: 60 * 60 * 24
|
|
2295
2295
|
// 24 hours
|
|
2296
2296
|
});
|
|
2297
|
-
await
|
|
2297
|
+
await db2.prepare("UPDATE users SET last_login_at = ? WHERE id = ?").bind((/* @__PURE__ */ new Date()).getTime(), user.id).run();
|
|
2298
2298
|
await cache.delete(cache.generateKey("user", user.id));
|
|
2299
2299
|
await cache.delete(cache.generateKey("user", `email:${normalizedEmail}`));
|
|
2300
2300
|
return c.json({
|
|
@@ -2341,8 +2341,8 @@ authRoutes.get("/me", requireAuth(), async (c) => {
|
|
|
2341
2341
|
if (!user) {
|
|
2342
2342
|
return c.json({ error: "Not authenticated" }, 401);
|
|
2343
2343
|
}
|
|
2344
|
-
const
|
|
2345
|
-
const userData = await
|
|
2344
|
+
const db2 = c.env.DB;
|
|
2345
|
+
const userData = await db2.prepare("SELECT id, email, username, first_name, last_name, role, created_at FROM users WHERE id = ?").bind(user.userId).first();
|
|
2346
2346
|
if (!userData) {
|
|
2347
2347
|
return c.json({ error: "User not found" }, 404);
|
|
2348
2348
|
}
|
|
@@ -2374,7 +2374,7 @@ authRoutes.post("/refresh", requireAuth(), async (c) => {
|
|
|
2374
2374
|
});
|
|
2375
2375
|
authRoutes.post("/register/form", async (c) => {
|
|
2376
2376
|
try {
|
|
2377
|
-
const
|
|
2377
|
+
const db2 = c.env.DB;
|
|
2378
2378
|
const formData = await c.req.formData();
|
|
2379
2379
|
const requestData = {
|
|
2380
2380
|
email: formData.get("email"),
|
|
@@ -2385,12 +2385,12 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2385
2385
|
};
|
|
2386
2386
|
const normalizedEmail = requestData.email?.toLowerCase();
|
|
2387
2387
|
requestData.email = normalizedEmail;
|
|
2388
|
-
const validationSchema = await authValidationService.buildRegistrationSchema(
|
|
2388
|
+
const validationSchema = await authValidationService.buildRegistrationSchema(db2);
|
|
2389
2389
|
const validation = await validationSchema.safeParseAsync(requestData);
|
|
2390
2390
|
if (!validation.success) {
|
|
2391
2391
|
return c.html(html`
|
|
2392
2392
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
2393
|
-
${validation.error.
|
|
2393
|
+
${validation.error.issues.map((err) => err.message).join(", ")}
|
|
2394
2394
|
</div>
|
|
2395
2395
|
`);
|
|
2396
2396
|
}
|
|
@@ -2399,7 +2399,7 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2399
2399
|
const username = validatedData.username || authValidationService.generateDefaultValue("username", validatedData);
|
|
2400
2400
|
const firstName = validatedData.firstName || authValidationService.generateDefaultValue("firstName", validatedData);
|
|
2401
2401
|
const lastName = validatedData.lastName || authValidationService.generateDefaultValue("lastName", validatedData);
|
|
2402
|
-
const existingUser = await
|
|
2402
|
+
const existingUser = await db2.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind(normalizedEmail, username).first();
|
|
2403
2403
|
if (existingUser) {
|
|
2404
2404
|
return c.html(html`
|
|
2405
2405
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -2410,7 +2410,7 @@ authRoutes.post("/register/form", async (c) => {
|
|
|
2410
2410
|
const passwordHash = await AuthManager.hashPassword(password);
|
|
2411
2411
|
const userId = crypto.randomUUID();
|
|
2412
2412
|
const now = /* @__PURE__ */ new Date();
|
|
2413
|
-
await
|
|
2413
|
+
await db2.prepare(`
|
|
2414
2414
|
INSERT INTO users (id, email, username, first_name, last_name, password_hash, role, is_active, created_at, updated_at)
|
|
2415
2415
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2416
2416
|
`).bind(
|
|
@@ -2465,12 +2465,12 @@ authRoutes.post("/login/form", async (c) => {
|
|
|
2465
2465
|
if (!validation.success) {
|
|
2466
2466
|
return c.html(html`
|
|
2467
2467
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
2468
|
-
${validation.error.
|
|
2468
|
+
${validation.error.issues.map((err) => err.message).join(", ")}
|
|
2469
2469
|
</div>
|
|
2470
2470
|
`);
|
|
2471
2471
|
}
|
|
2472
|
-
const
|
|
2473
|
-
const user = await
|
|
2472
|
+
const db2 = c.env.DB;
|
|
2473
|
+
const user = await db2.prepare("SELECT * FROM users WHERE email = ? AND is_active = 1").bind(normalizedEmail).first();
|
|
2474
2474
|
if (!user) {
|
|
2475
2475
|
return c.html(html`
|
|
2476
2476
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -2495,7 +2495,7 @@ authRoutes.post("/login/form", async (c) => {
|
|
|
2495
2495
|
maxAge: 60 * 60 * 24
|
|
2496
2496
|
// 24 hours
|
|
2497
2497
|
});
|
|
2498
|
-
await
|
|
2498
|
+
await db2.prepare("UPDATE users SET last_login_at = ? WHERE id = ?").bind((/* @__PURE__ */ new Date()).getTime(), user.id).run();
|
|
2499
2499
|
return c.html(html`
|
|
2500
2500
|
<div id="form-response">
|
|
2501
2501
|
<div class="rounded-lg bg-green-100 dark:bg-lime-500/10 p-4 ring-1 ring-green-400 dark:ring-lime-500/20">
|
|
@@ -2526,8 +2526,8 @@ authRoutes.post("/login/form", async (c) => {
|
|
|
2526
2526
|
});
|
|
2527
2527
|
authRoutes.post("/seed-admin", async (c) => {
|
|
2528
2528
|
try {
|
|
2529
|
-
const
|
|
2530
|
-
await
|
|
2529
|
+
const db2 = c.env.DB;
|
|
2530
|
+
await db2.prepare(`
|
|
2531
2531
|
CREATE TABLE IF NOT EXISTS users (
|
|
2532
2532
|
id TEXT PRIMARY KEY,
|
|
2533
2533
|
email TEXT NOT NULL UNIQUE,
|
|
@@ -2543,10 +2543,10 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2543
2543
|
updated_at INTEGER NOT NULL
|
|
2544
2544
|
)
|
|
2545
2545
|
`).run();
|
|
2546
|
-
const existingAdmin = await
|
|
2546
|
+
const existingAdmin = await db2.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
|
|
2547
2547
|
if (existingAdmin) {
|
|
2548
2548
|
const passwordHash2 = await AuthManager.hashPassword("sonicjs!");
|
|
2549
|
-
await
|
|
2549
|
+
await db2.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
|
|
2550
2550
|
return c.json({
|
|
2551
2551
|
message: "Admin user already exists (password updated)",
|
|
2552
2552
|
user: {
|
|
@@ -2561,7 +2561,7 @@ authRoutes.post("/seed-admin", async (c) => {
|
|
|
2561
2561
|
const userId = "admin-user-id";
|
|
2562
2562
|
const now = Date.now();
|
|
2563
2563
|
const adminEmail = "admin@sonicjs.com".toLowerCase();
|
|
2564
|
-
await
|
|
2564
|
+
await db2.prepare(`
|
|
2565
2565
|
INSERT INTO users (id, email, username, first_name, last_name, password_hash, role, is_active, created_at, updated_at)
|
|
2566
2566
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2567
2567
|
`).bind(
|
|
@@ -2608,8 +2608,8 @@ authRoutes.get("/accept-invitation", async (c) => {
|
|
|
2608
2608
|
</html>
|
|
2609
2609
|
`);
|
|
2610
2610
|
}
|
|
2611
|
-
const
|
|
2612
|
-
const userStmt =
|
|
2611
|
+
const db2 = c.env.DB;
|
|
2612
|
+
const userStmt = db2.prepare(`
|
|
2613
2613
|
SELECT id, email, first_name, last_name, role, invited_at
|
|
2614
2614
|
FROM users
|
|
2615
2615
|
WHERE invitation_token = ? AND is_active = 0
|
|
@@ -2755,8 +2755,8 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
2755
2755
|
if (password.length < 8) {
|
|
2756
2756
|
return c.json({ error: "Password must be at least 8 characters long" }, 400);
|
|
2757
2757
|
}
|
|
2758
|
-
const
|
|
2759
|
-
const userStmt =
|
|
2758
|
+
const db2 = c.env.DB;
|
|
2759
|
+
const userStmt = db2.prepare(`
|
|
2760
2760
|
SELECT id, email, first_name, last_name, role, invited_at
|
|
2761
2761
|
FROM users
|
|
2762
2762
|
WHERE invitation_token = ? AND is_active = 0
|
|
@@ -2770,7 +2770,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
2770
2770
|
if (invitationAge > maxAge) {
|
|
2771
2771
|
return c.json({ error: "Invitation has expired" }, 400);
|
|
2772
2772
|
}
|
|
2773
|
-
const existingUsernameStmt =
|
|
2773
|
+
const existingUsernameStmt = db2.prepare(`
|
|
2774
2774
|
SELECT id FROM users WHERE username = ? AND id != ?
|
|
2775
2775
|
`);
|
|
2776
2776
|
const existingUsername = await existingUsernameStmt.bind(username, invitedUser.id).first();
|
|
@@ -2778,7 +2778,7 @@ authRoutes.post("/accept-invitation", async (c) => {
|
|
|
2778
2778
|
return c.json({ error: "Username is already taken" }, 400);
|
|
2779
2779
|
}
|
|
2780
2780
|
const passwordHash = await AuthManager.hashPassword(password);
|
|
2781
|
-
const updateStmt =
|
|
2781
|
+
const updateStmt = db2.prepare(`
|
|
2782
2782
|
UPDATE users SET
|
|
2783
2783
|
username = ?,
|
|
2784
2784
|
password_hash = ?,
|
|
@@ -2821,8 +2821,8 @@ authRoutes.post("/request-password-reset", async (c) => {
|
|
|
2821
2821
|
if (!emailRegex.test(email)) {
|
|
2822
2822
|
return c.json({ error: "Please enter a valid email address" }, 400);
|
|
2823
2823
|
}
|
|
2824
|
-
const
|
|
2825
|
-
const userStmt =
|
|
2824
|
+
const db2 = c.env.DB;
|
|
2825
|
+
const userStmt = db2.prepare(`
|
|
2826
2826
|
SELECT id, email, first_name, last_name FROM users
|
|
2827
2827
|
WHERE email = ? AND is_active = 1
|
|
2828
2828
|
`);
|
|
@@ -2835,7 +2835,7 @@ authRoutes.post("/request-password-reset", async (c) => {
|
|
|
2835
2835
|
}
|
|
2836
2836
|
const resetToken = crypto.randomUUID();
|
|
2837
2837
|
const resetExpires = Date.now() + 60 * 60 * 1e3;
|
|
2838
|
-
const updateStmt =
|
|
2838
|
+
const updateStmt = db2.prepare(`
|
|
2839
2839
|
UPDATE users SET
|
|
2840
2840
|
password_reset_token = ?,
|
|
2841
2841
|
password_reset_expires = ?,
|
|
@@ -2875,8 +2875,8 @@ authRoutes.get("/reset-password", async (c) => {
|
|
|
2875
2875
|
</html>
|
|
2876
2876
|
`);
|
|
2877
2877
|
}
|
|
2878
|
-
const
|
|
2879
|
-
const userStmt =
|
|
2878
|
+
const db2 = c.env.DB;
|
|
2879
|
+
const userStmt = db2.prepare(`
|
|
2880
2880
|
SELECT id, email, first_name, last_name, password_reset_expires
|
|
2881
2881
|
FROM users
|
|
2882
2882
|
WHERE password_reset_token = ? AND is_active = 1
|
|
@@ -3013,8 +3013,8 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3013
3013
|
if (password.length < 8) {
|
|
3014
3014
|
return c.json({ error: "Password must be at least 8 characters long" }, 400);
|
|
3015
3015
|
}
|
|
3016
|
-
const
|
|
3017
|
-
const userStmt =
|
|
3016
|
+
const db2 = c.env.DB;
|
|
3017
|
+
const userStmt = db2.prepare(`
|
|
3018
3018
|
SELECT id, email, password_hash, password_reset_expires
|
|
3019
3019
|
FROM users
|
|
3020
3020
|
WHERE password_reset_token = ? AND is_active = 1
|
|
@@ -3028,7 +3028,7 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3028
3028
|
}
|
|
3029
3029
|
const newPasswordHash = await AuthManager.hashPassword(password);
|
|
3030
3030
|
try {
|
|
3031
|
-
const historyStmt =
|
|
3031
|
+
const historyStmt = db2.prepare(`
|
|
3032
3032
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
3033
3033
|
VALUES (?, ?, ?, ?)
|
|
3034
3034
|
`);
|
|
@@ -3041,7 +3041,7 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3041
3041
|
} catch (historyError) {
|
|
3042
3042
|
console.warn("Could not store password history:", historyError);
|
|
3043
3043
|
}
|
|
3044
|
-
const updateStmt =
|
|
3044
|
+
const updateStmt = db2.prepare(`
|
|
3045
3045
|
UPDATE users SET
|
|
3046
3046
|
password_hash = ?,
|
|
3047
3047
|
password_reset_token = NULL,
|
|
@@ -3061,19 +3061,269 @@ authRoutes.post("/reset-password", async (c) => {
|
|
|
3061
3061
|
}
|
|
3062
3062
|
});
|
|
3063
3063
|
var auth_default = authRoutes;
|
|
3064
|
+
var app = new Hono();
|
|
3065
|
+
app.post("/test-cleanup", async (c) => {
|
|
3066
|
+
const db2 = c.env.DB;
|
|
3067
|
+
if (c.env.ENVIRONMENT === "production") {
|
|
3068
|
+
return c.json({ error: "Cleanup endpoint not available in production" }, 403);
|
|
3069
|
+
}
|
|
3070
|
+
try {
|
|
3071
|
+
let deletedCount = 0;
|
|
3072
|
+
await db2.prepare(`
|
|
3073
|
+
DELETE FROM content_versions
|
|
3074
|
+
WHERE content_id IN (
|
|
3075
|
+
SELECT id FROM content
|
|
3076
|
+
WHERE title LIKE 'Test %' OR title LIKE '%E2E%' OR title LIKE '%Playwright%' OR title LIKE '%Sample%'
|
|
3077
|
+
)
|
|
3078
|
+
`).run();
|
|
3079
|
+
await db2.prepare(`
|
|
3080
|
+
DELETE FROM workflow_history
|
|
3081
|
+
WHERE content_id IN (
|
|
3082
|
+
SELECT id FROM content
|
|
3083
|
+
WHERE title LIKE 'Test %' OR title LIKE '%E2E%' OR title LIKE '%Playwright%' OR title LIKE '%Sample%'
|
|
3084
|
+
)
|
|
3085
|
+
`).run();
|
|
3086
|
+
try {
|
|
3087
|
+
await db2.prepare(`
|
|
3088
|
+
DELETE FROM content_data
|
|
3089
|
+
WHERE content_id IN (
|
|
3090
|
+
SELECT id FROM content
|
|
3091
|
+
WHERE title LIKE 'Test %' OR title LIKE '%E2E%' OR title LIKE '%Playwright%' OR title LIKE '%Sample%'
|
|
3092
|
+
)
|
|
3093
|
+
`).run();
|
|
3094
|
+
} catch (e) {
|
|
3095
|
+
}
|
|
3096
|
+
const contentResult = await db2.prepare(`
|
|
3097
|
+
DELETE FROM content
|
|
3098
|
+
WHERE title LIKE 'Test %' OR title LIKE '%E2E%' OR title LIKE '%Playwright%' OR title LIKE '%Sample%'
|
|
3099
|
+
`).run();
|
|
3100
|
+
deletedCount += contentResult.meta?.changes || 0;
|
|
3101
|
+
await db2.prepare(`
|
|
3102
|
+
DELETE FROM api_tokens
|
|
3103
|
+
WHERE user_id IN (
|
|
3104
|
+
SELECT id FROM users
|
|
3105
|
+
WHERE email != 'admin@sonicjs.com' AND (email LIKE '%test%' OR email LIKE '%example.com%')
|
|
3106
|
+
)
|
|
3107
|
+
`).run();
|
|
3108
|
+
await db2.prepare(`
|
|
3109
|
+
DELETE FROM media
|
|
3110
|
+
WHERE uploaded_by IN (
|
|
3111
|
+
SELECT id FROM users
|
|
3112
|
+
WHERE email != 'admin@sonicjs.com' AND (email LIKE '%test%' OR email LIKE '%example.com%')
|
|
3113
|
+
)
|
|
3114
|
+
`).run();
|
|
3115
|
+
const usersResult = await db2.prepare(`
|
|
3116
|
+
DELETE FROM users
|
|
3117
|
+
WHERE email != 'admin@sonicjs.com' AND (email LIKE '%test%' OR email LIKE '%example.com%')
|
|
3118
|
+
`).run();
|
|
3119
|
+
deletedCount += usersResult.meta?.changes || 0;
|
|
3120
|
+
try {
|
|
3121
|
+
await db2.prepare(`
|
|
3122
|
+
DELETE FROM collection_fields
|
|
3123
|
+
WHERE collection_id IN (
|
|
3124
|
+
SELECT id FROM collections
|
|
3125
|
+
WHERE name LIKE 'test_%' OR name IN ('blog_posts', 'test_collection', 'products', 'articles')
|
|
3126
|
+
)
|
|
3127
|
+
`).run();
|
|
3128
|
+
} catch (e) {
|
|
3129
|
+
}
|
|
3130
|
+
await db2.prepare(`
|
|
3131
|
+
DELETE FROM content
|
|
3132
|
+
WHERE collection_id IN (
|
|
3133
|
+
SELECT id FROM collections
|
|
3134
|
+
WHERE name LIKE 'test_%' OR name IN ('blog_posts', 'test_collection', 'products', 'articles')
|
|
3135
|
+
)
|
|
3136
|
+
`).run();
|
|
3137
|
+
const collectionsResult = await db2.prepare(`
|
|
3138
|
+
DELETE FROM collections
|
|
3139
|
+
WHERE name LIKE 'test_%' OR name IN ('blog_posts', 'test_collection', 'products', 'articles')
|
|
3140
|
+
`).run();
|
|
3141
|
+
deletedCount += collectionsResult.meta?.changes || 0;
|
|
3142
|
+
try {
|
|
3143
|
+
await db2.prepare(`
|
|
3144
|
+
DELETE FROM content_data WHERE content_id NOT IN (SELECT id FROM content)
|
|
3145
|
+
`).run();
|
|
3146
|
+
} catch (e) {
|
|
3147
|
+
}
|
|
3148
|
+
try {
|
|
3149
|
+
await db2.prepare(`
|
|
3150
|
+
DELETE FROM collection_fields WHERE collection_id NOT IN (SELECT id FROM collections)
|
|
3151
|
+
`).run();
|
|
3152
|
+
} catch (e) {
|
|
3153
|
+
}
|
|
3154
|
+
try {
|
|
3155
|
+
await db2.prepare(`
|
|
3156
|
+
DELETE FROM content_versions WHERE content_id NOT IN (SELECT id FROM content)
|
|
3157
|
+
`).run();
|
|
3158
|
+
} catch (e) {
|
|
3159
|
+
}
|
|
3160
|
+
try {
|
|
3161
|
+
await db2.prepare(`
|
|
3162
|
+
DELETE FROM workflow_history WHERE content_id NOT IN (SELECT id FROM content)
|
|
3163
|
+
`).run();
|
|
3164
|
+
} catch (e) {
|
|
3165
|
+
}
|
|
3166
|
+
await db2.prepare(`
|
|
3167
|
+
DELETE FROM activity_logs
|
|
3168
|
+
WHERE id NOT IN (
|
|
3169
|
+
SELECT id FROM activity_logs
|
|
3170
|
+
ORDER BY created_at DESC
|
|
3171
|
+
LIMIT 100
|
|
3172
|
+
)
|
|
3173
|
+
`).run();
|
|
3174
|
+
return c.json({
|
|
3175
|
+
success: true,
|
|
3176
|
+
deletedCount,
|
|
3177
|
+
message: "Test data cleaned up successfully"
|
|
3178
|
+
});
|
|
3179
|
+
} catch (error) {
|
|
3180
|
+
console.error("Test cleanup error:", error);
|
|
3181
|
+
return c.json({
|
|
3182
|
+
success: false,
|
|
3183
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3184
|
+
}, 500);
|
|
3185
|
+
}
|
|
3186
|
+
});
|
|
3187
|
+
app.post("/test-cleanup/users", async (c) => {
|
|
3188
|
+
const db2 = c.env.DB;
|
|
3189
|
+
if (c.env.ENVIRONMENT === "production") {
|
|
3190
|
+
return c.json({ error: "Cleanup endpoint not available in production" }, 403);
|
|
3191
|
+
}
|
|
3192
|
+
try {
|
|
3193
|
+
const result = await db2.prepare(`
|
|
3194
|
+
DELETE FROM users
|
|
3195
|
+
WHERE email != 'admin@sonicjs.com'
|
|
3196
|
+
AND (
|
|
3197
|
+
email LIKE '%test%'
|
|
3198
|
+
OR email LIKE '%example.com%'
|
|
3199
|
+
OR first_name = 'Test'
|
|
3200
|
+
)
|
|
3201
|
+
`).run();
|
|
3202
|
+
return c.json({
|
|
3203
|
+
success: true,
|
|
3204
|
+
deletedCount: result.meta?.changes || 0,
|
|
3205
|
+
message: "Test users cleaned up successfully"
|
|
3206
|
+
});
|
|
3207
|
+
} catch (error) {
|
|
3208
|
+
console.error("User cleanup error:", error);
|
|
3209
|
+
return c.json({
|
|
3210
|
+
success: false,
|
|
3211
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3212
|
+
}, 500);
|
|
3213
|
+
}
|
|
3214
|
+
});
|
|
3215
|
+
app.post("/test-cleanup/collections", async (c) => {
|
|
3216
|
+
const db2 = c.env.DB;
|
|
3217
|
+
if (c.env.ENVIRONMENT === "production") {
|
|
3218
|
+
return c.json({ error: "Cleanup endpoint not available in production" }, 403);
|
|
3219
|
+
}
|
|
3220
|
+
try {
|
|
3221
|
+
let deletedCount = 0;
|
|
3222
|
+
const collections = await db2.prepare(`
|
|
3223
|
+
SELECT id FROM collections
|
|
3224
|
+
WHERE name LIKE 'test_%'
|
|
3225
|
+
OR name IN ('blog_posts', 'test_collection', 'products', 'articles')
|
|
3226
|
+
`).all();
|
|
3227
|
+
if (collections.results && collections.results.length > 0) {
|
|
3228
|
+
const collectionIds = collections.results.map((c2) => c2.id);
|
|
3229
|
+
for (const id of collectionIds) {
|
|
3230
|
+
await db2.prepare("DELETE FROM collection_fields WHERE collection_id = ?").bind(id).run();
|
|
3231
|
+
}
|
|
3232
|
+
for (const id of collectionIds) {
|
|
3233
|
+
await db2.prepare("DELETE FROM content WHERE collection_id = ?").bind(id).run();
|
|
3234
|
+
}
|
|
3235
|
+
const result = await db2.prepare(`
|
|
3236
|
+
DELETE FROM collections
|
|
3237
|
+
WHERE id IN (${collectionIds.map(() => "?").join(",")})
|
|
3238
|
+
`).bind(...collectionIds).run();
|
|
3239
|
+
deletedCount = result.meta?.changes || 0;
|
|
3240
|
+
}
|
|
3241
|
+
return c.json({
|
|
3242
|
+
success: true,
|
|
3243
|
+
deletedCount,
|
|
3244
|
+
message: "Test collections cleaned up successfully"
|
|
3245
|
+
});
|
|
3246
|
+
} catch (error) {
|
|
3247
|
+
console.error("Collection cleanup error:", error);
|
|
3248
|
+
return c.json({
|
|
3249
|
+
success: false,
|
|
3250
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3251
|
+
}, 500);
|
|
3252
|
+
}
|
|
3253
|
+
});
|
|
3254
|
+
app.post("/test-cleanup/content", async (c) => {
|
|
3255
|
+
const db2 = c.env.DB;
|
|
3256
|
+
if (c.env.ENVIRONMENT === "production") {
|
|
3257
|
+
return c.json({ error: "Cleanup endpoint not available in production" }, 403);
|
|
3258
|
+
}
|
|
3259
|
+
try {
|
|
3260
|
+
const result = await db2.prepare(`
|
|
3261
|
+
DELETE FROM content
|
|
3262
|
+
WHERE title LIKE 'Test %'
|
|
3263
|
+
OR title LIKE '%E2E%'
|
|
3264
|
+
OR title LIKE '%Playwright%'
|
|
3265
|
+
OR title LIKE '%Sample%'
|
|
3266
|
+
`).run();
|
|
3267
|
+
await db2.prepare(`
|
|
3268
|
+
DELETE FROM content_data
|
|
3269
|
+
WHERE content_id NOT IN (SELECT id FROM content)
|
|
3270
|
+
`).run();
|
|
3271
|
+
return c.json({
|
|
3272
|
+
success: true,
|
|
3273
|
+
deletedCount: result.meta?.changes || 0,
|
|
3274
|
+
message: "Test content cleaned up successfully"
|
|
3275
|
+
});
|
|
3276
|
+
} catch (error) {
|
|
3277
|
+
console.error("Content cleanup error:", error);
|
|
3278
|
+
return c.json({
|
|
3279
|
+
success: false,
|
|
3280
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
3281
|
+
}, 500);
|
|
3282
|
+
}
|
|
3283
|
+
});
|
|
3284
|
+
var test_cleanup_default = app;
|
|
3064
3285
|
|
|
3065
3286
|
// src/templates/pages/admin-content-form.template.ts
|
|
3066
3287
|
init_admin_layout_catalyst_template();
|
|
3067
3288
|
|
|
3068
3289
|
// src/templates/components/dynamic-field.template.ts
|
|
3069
3290
|
function renderDynamicField(field, options = {}) {
|
|
3070
|
-
const { value = "", errors = [], disabled = false, className = "" } = options;
|
|
3291
|
+
const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {} } = options;
|
|
3071
3292
|
const opts = field.field_options || {};
|
|
3072
3293
|
const required = field.is_required ? "required" : "";
|
|
3073
3294
|
const baseClasses = `w-full rounded-lg px-3 py-2 text-sm text-zinc-950 dark:text-white bg-white dark:bg-zinc-800 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow ${className}`;
|
|
3074
3295
|
const errorClasses = errors.length > 0 ? "ring-pink-600 dark:ring-pink-500 focus:ring-pink-600 dark:focus:ring-pink-500" : "";
|
|
3075
3296
|
const fieldId = `field-${field.field_name}`;
|
|
3076
3297
|
const fieldName = field.field_name;
|
|
3298
|
+
let fallbackToTextarea = false;
|
|
3299
|
+
let fallbackWarning = "";
|
|
3300
|
+
if (field.field_type === "quill" && !pluginStatuses.quillEnabled) {
|
|
3301
|
+
fallbackToTextarea = true;
|
|
3302
|
+
fallbackWarning = "\u26A0\uFE0F Quill Editor plugin is inactive. Using textarea fallback.";
|
|
3303
|
+
} else if (field.field_type === "mdxeditor" && !pluginStatuses.mdxeditorEnabled) {
|
|
3304
|
+
fallbackToTextarea = true;
|
|
3305
|
+
fallbackWarning = "\u26A0\uFE0F MDXEditor plugin is inactive. Using textarea fallback.";
|
|
3306
|
+
} else if (field.field_type === "tinymce" && !pluginStatuses.tinymceEnabled) {
|
|
3307
|
+
fallbackToTextarea = true;
|
|
3308
|
+
fallbackWarning = "\u26A0\uFE0F TinyMCE plugin is inactive. Using textarea fallback.";
|
|
3309
|
+
}
|
|
3310
|
+
if (fallbackToTextarea) {
|
|
3311
|
+
return `
|
|
3312
|
+
<div>
|
|
3313
|
+
${fallbackWarning ? `<div class="mb-2 px-3 py-2 bg-amber-50 dark:bg-amber-900/20 text-amber-900 dark:text-amber-200 text-xs rounded-lg border border-amber-200 dark:border-amber-800">${fallbackWarning}</div>` : ""}
|
|
3314
|
+
<textarea
|
|
3315
|
+
id="${fieldId}"
|
|
3316
|
+
name="${fieldName}"
|
|
3317
|
+
rows="${opts.rows || opts.height ? Math.floor(opts.height / 25) : 10}"
|
|
3318
|
+
placeholder="${opts.placeholder || ""}"
|
|
3319
|
+
maxlength="${opts.maxLength || ""}"
|
|
3320
|
+
class="${baseClasses} ${errorClasses} resize-y"
|
|
3321
|
+
${required}
|
|
3322
|
+
${disabled ? "disabled" : ""}
|
|
3323
|
+
>${escapeHtml2(value)}</textarea>
|
|
3324
|
+
</div>
|
|
3325
|
+
`;
|
|
3326
|
+
}
|
|
3077
3327
|
let fieldHTML = "";
|
|
3078
3328
|
switch (field.field_type) {
|
|
3079
3329
|
case "text":
|
|
@@ -3996,9 +4246,9 @@ function createQuillEditorPlugin() {
|
|
|
3996
4246
|
}
|
|
3997
4247
|
createQuillEditorPlugin();
|
|
3998
4248
|
|
|
3999
|
-
// src/plugins/available/
|
|
4249
|
+
// src/plugins/available/easy-mdx/index.ts
|
|
4000
4250
|
var builder2 = PluginBuilder.create({
|
|
4001
|
-
name: "
|
|
4251
|
+
name: "easy-mdx",
|
|
4002
4252
|
version: "1.0.0",
|
|
4003
4253
|
description: "Lightweight markdown editor with live preview"
|
|
4004
4254
|
});
|
|
@@ -4214,17 +4464,25 @@ function renderContentFormPage(data) {
|
|
|
4214
4464
|
if (fieldName === "slug") return data.slug || data.data?.[fieldName] || "";
|
|
4215
4465
|
return data.data?.[fieldName] || "";
|
|
4216
4466
|
};
|
|
4467
|
+
const pluginStatuses = {
|
|
4468
|
+
quillEnabled: data.quillEnabled || false,
|
|
4469
|
+
mdxeditorEnabled: data.mdxeditorEnabled || false,
|
|
4470
|
+
tinymceEnabled: data.tinymceEnabled || false
|
|
4471
|
+
};
|
|
4217
4472
|
const coreFieldsHTML = coreFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
|
|
4218
4473
|
value: getFieldValue(field.field_name),
|
|
4219
|
-
errors: data.validationErrors?.[field.field_name] || []
|
|
4474
|
+
errors: data.validationErrors?.[field.field_name] || [],
|
|
4475
|
+
pluginStatuses
|
|
4220
4476
|
}));
|
|
4221
4477
|
const contentFieldsHTML = contentFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
|
|
4222
4478
|
value: getFieldValue(field.field_name),
|
|
4223
|
-
errors: data.validationErrors?.[field.field_name] || []
|
|
4479
|
+
errors: data.validationErrors?.[field.field_name] || [],
|
|
4480
|
+
pluginStatuses
|
|
4224
4481
|
}));
|
|
4225
4482
|
const metaFieldsHTML = metaFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
|
|
4226
4483
|
value: getFieldValue(field.field_name),
|
|
4227
|
-
errors: data.validationErrors?.[field.field_name] || []
|
|
4484
|
+
errors: data.validationErrors?.[field.field_name] || [],
|
|
4485
|
+
pluginStatuses
|
|
4228
4486
|
}));
|
|
4229
4487
|
const pageContent = `
|
|
4230
4488
|
<div class="space-y-6">
|
|
@@ -5653,9 +5911,9 @@ function escapeHtml3(text) {
|
|
|
5653
5911
|
}
|
|
5654
5912
|
|
|
5655
5913
|
// src/middleware/plugin-middleware.ts
|
|
5656
|
-
async function isPluginActive2(
|
|
5914
|
+
async function isPluginActive2(db2, pluginId) {
|
|
5657
5915
|
try {
|
|
5658
|
-
const result = await
|
|
5916
|
+
const result = await db2.prepare("SELECT status FROM plugins WHERE id = ?").bind(pluginId).first();
|
|
5659
5917
|
return result?.status === "active";
|
|
5660
5918
|
} catch (error) {
|
|
5661
5919
|
console.error(`[isPluginActive] Error checking plugin status for ${pluginId}:`, error);
|
|
@@ -5666,12 +5924,12 @@ async function isPluginActive2(db, pluginId) {
|
|
|
5666
5924
|
// src/routes/admin-content.ts
|
|
5667
5925
|
var adminContentRoutes = new Hono();
|
|
5668
5926
|
adminContentRoutes.use("*", requireAuth());
|
|
5669
|
-
async function getCollectionFields(
|
|
5927
|
+
async function getCollectionFields(db2, collectionId) {
|
|
5670
5928
|
const cache = getCacheService(CACHE_CONFIGS.collection);
|
|
5671
5929
|
return cache.getOrSet(
|
|
5672
5930
|
cache.generateKey("fields", collectionId),
|
|
5673
5931
|
async () => {
|
|
5674
|
-
const collectionStmt =
|
|
5932
|
+
const collectionStmt = db2.prepare("SELECT schema FROM collections WHERE id = ?");
|
|
5675
5933
|
const collectionRow = await collectionStmt.bind(collectionId).first();
|
|
5676
5934
|
if (collectionRow && collectionRow.schema) {
|
|
5677
5935
|
try {
|
|
@@ -5702,7 +5960,7 @@ async function getCollectionFields(db, collectionId) {
|
|
|
5702
5960
|
console.error("Error parsing collection schema:", e);
|
|
5703
5961
|
}
|
|
5704
5962
|
}
|
|
5705
|
-
const stmt =
|
|
5963
|
+
const stmt = db2.prepare(`
|
|
5706
5964
|
SELECT * FROM content_fields
|
|
5707
5965
|
WHERE collection_id = ?
|
|
5708
5966
|
ORDER BY field_order ASC
|
|
@@ -5721,12 +5979,12 @@ async function getCollectionFields(db, collectionId) {
|
|
|
5721
5979
|
}
|
|
5722
5980
|
);
|
|
5723
5981
|
}
|
|
5724
|
-
async function getCollection(
|
|
5982
|
+
async function getCollection(db2, collectionId) {
|
|
5725
5983
|
const cache = getCacheService(CACHE_CONFIGS.collection);
|
|
5726
5984
|
return cache.getOrSet(
|
|
5727
5985
|
cache.generateKey("collection", collectionId),
|
|
5728
5986
|
async () => {
|
|
5729
|
-
const stmt =
|
|
5987
|
+
const stmt = db2.prepare("SELECT * FROM collections WHERE id = ? AND is_active = 1");
|
|
5730
5988
|
const collection = await stmt.bind(collectionId).first();
|
|
5731
5989
|
if (!collection) return null;
|
|
5732
5990
|
return {
|
|
@@ -5743,14 +6001,14 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
5743
6001
|
try {
|
|
5744
6002
|
const user = c.get("user");
|
|
5745
6003
|
const url = new URL(c.req.url);
|
|
5746
|
-
const
|
|
6004
|
+
const db2 = c.env.DB;
|
|
5747
6005
|
const page = parseInt(url.searchParams.get("page") || "1");
|
|
5748
6006
|
const limit = parseInt(url.searchParams.get("limit") || "20");
|
|
5749
6007
|
const modelName = url.searchParams.get("model") || "all";
|
|
5750
6008
|
const status = url.searchParams.get("status") || "all";
|
|
5751
6009
|
const search = url.searchParams.get("search") || "";
|
|
5752
6010
|
const offset = (page - 1) * limit;
|
|
5753
|
-
const collectionsStmt =
|
|
6011
|
+
const collectionsStmt = db2.prepare("SELECT id, name, display_name FROM collections WHERE is_active = 1 ORDER BY display_name");
|
|
5754
6012
|
const { results: collectionsResults } = await collectionsStmt.all();
|
|
5755
6013
|
const models = (collectionsResults || []).map((row) => ({
|
|
5756
6014
|
name: row.name,
|
|
@@ -5776,7 +6034,7 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
5776
6034
|
conditions.push("c.status = 'deleted'");
|
|
5777
6035
|
}
|
|
5778
6036
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5779
|
-
const countStmt =
|
|
6037
|
+
const countStmt = db2.prepare(`
|
|
5780
6038
|
SELECT COUNT(*) as count
|
|
5781
6039
|
FROM content c
|
|
5782
6040
|
JOIN collections col ON c.collection_id = col.id
|
|
@@ -5784,7 +6042,7 @@ adminContentRoutes.get("/", async (c) => {
|
|
|
5784
6042
|
`);
|
|
5785
6043
|
const countResult = await countStmt.bind(...params).first();
|
|
5786
6044
|
const totalItems = countResult?.count || 0;
|
|
5787
|
-
const contentStmt =
|
|
6045
|
+
const contentStmt = db2.prepare(`
|
|
5788
6046
|
SELECT c.id, c.title, c.slug, c.status, c.created_at, c.updated_at,
|
|
5789
6047
|
col.name as collection_name, col.display_name as collection_display_name,
|
|
5790
6048
|
u.first_name, u.last_name, u.email as author_email
|
|
@@ -5885,8 +6143,8 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
5885
6143
|
const url = new URL(c.req.url);
|
|
5886
6144
|
const collectionId = url.searchParams.get("collection");
|
|
5887
6145
|
if (!collectionId) {
|
|
5888
|
-
const
|
|
5889
|
-
const collectionsStmt =
|
|
6146
|
+
const db3 = c.env.DB;
|
|
6147
|
+
const collectionsStmt = db3.prepare("SELECT id, name, display_name, description FROM collections WHERE is_active = 1 ORDER BY display_name");
|
|
5890
6148
|
const { results } = await collectionsStmt.all();
|
|
5891
6149
|
const collections = (results || []).map((row) => ({
|
|
5892
6150
|
id: row.id,
|
|
@@ -5927,8 +6185,8 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
5927
6185
|
`;
|
|
5928
6186
|
return c.html(selectionHTML);
|
|
5929
6187
|
}
|
|
5930
|
-
const
|
|
5931
|
-
const collection = await getCollection(
|
|
6188
|
+
const db2 = c.env.DB;
|
|
6189
|
+
const collection = await getCollection(db2, collectionId);
|
|
5932
6190
|
if (!collection) {
|
|
5933
6191
|
const formData2 = {
|
|
5934
6192
|
collection: { id: "", name: "", display_name: "Unknown", schema: {} },
|
|
@@ -5942,28 +6200,28 @@ adminContentRoutes.get("/new", async (c) => {
|
|
|
5942
6200
|
};
|
|
5943
6201
|
return c.html(renderContentFormPage(formData2));
|
|
5944
6202
|
}
|
|
5945
|
-
const fields = await getCollectionFields(
|
|
5946
|
-
const workflowEnabled = await isPluginActive2(
|
|
5947
|
-
const tinymceEnabled = await isPluginActive2(
|
|
6203
|
+
const fields = await getCollectionFields(db2, collectionId);
|
|
6204
|
+
const workflowEnabled = await isPluginActive2(db2, "workflow");
|
|
6205
|
+
const tinymceEnabled = await isPluginActive2(db2, "tinymce-plugin");
|
|
5948
6206
|
let tinymceSettings;
|
|
5949
6207
|
if (tinymceEnabled) {
|
|
5950
|
-
const pluginService = new PluginService(
|
|
6208
|
+
const pluginService = new PluginService(db2);
|
|
5951
6209
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
5952
6210
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
5953
6211
|
}
|
|
5954
|
-
const quillEnabled = await isPluginActive2(
|
|
6212
|
+
const quillEnabled = await isPluginActive2(db2, "quill-editor");
|
|
5955
6213
|
let quillSettings;
|
|
5956
6214
|
if (quillEnabled) {
|
|
5957
|
-
const pluginService = new PluginService(
|
|
6215
|
+
const pluginService = new PluginService(db2);
|
|
5958
6216
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
5959
6217
|
quillSettings = quillPlugin?.settings;
|
|
5960
6218
|
}
|
|
5961
|
-
const mdxeditorEnabled = await isPluginActive2(
|
|
6219
|
+
const mdxeditorEnabled = await isPluginActive2(db2, "easy-mdx");
|
|
5962
6220
|
let mdxeditorSettings;
|
|
5963
6221
|
if (mdxeditorEnabled) {
|
|
5964
|
-
const pluginService = new PluginService(
|
|
5965
|
-
const
|
|
5966
|
-
mdxeditorSettings =
|
|
6222
|
+
const pluginService = new PluginService(db2);
|
|
6223
|
+
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
6224
|
+
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
5967
6225
|
}
|
|
5968
6226
|
console.log("[Content Form /new] Editor plugins status:", {
|
|
5969
6227
|
tinymce: tinymceEnabled,
|
|
@@ -6008,14 +6266,14 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
6008
6266
|
try {
|
|
6009
6267
|
const id = c.req.param("id");
|
|
6010
6268
|
const user = c.get("user");
|
|
6011
|
-
const
|
|
6269
|
+
const db2 = c.env.DB;
|
|
6012
6270
|
const url = new URL(c.req.url);
|
|
6013
6271
|
const referrerParams = url.searchParams.get("ref") || "";
|
|
6014
6272
|
const cache = getCacheService(CACHE_CONFIGS.content);
|
|
6015
6273
|
const content = await cache.getOrSet(
|
|
6016
6274
|
cache.generateKey("content", id),
|
|
6017
6275
|
async () => {
|
|
6018
|
-
const contentStmt =
|
|
6276
|
+
const contentStmt = db2.prepare(`
|
|
6019
6277
|
SELECT c.*, col.id as collection_id, col.name as collection_name,
|
|
6020
6278
|
col.display_name as collection_display_name, col.description as collection_description,
|
|
6021
6279
|
col.schema as collection_schema
|
|
@@ -6046,29 +6304,29 @@ adminContentRoutes.get("/:id/edit", async (c) => {
|
|
|
6046
6304
|
description: content.collection_description,
|
|
6047
6305
|
schema: content.collection_schema ? JSON.parse(content.collection_schema) : {}
|
|
6048
6306
|
};
|
|
6049
|
-
const fields = await getCollectionFields(
|
|
6307
|
+
const fields = await getCollectionFields(db2, content.collection_id);
|
|
6050
6308
|
const contentData = content.data ? JSON.parse(content.data) : {};
|
|
6051
|
-
const workflowEnabled = await isPluginActive2(
|
|
6052
|
-
const tinymceEnabled = await isPluginActive2(
|
|
6309
|
+
const workflowEnabled = await isPluginActive2(db2, "workflow");
|
|
6310
|
+
const tinymceEnabled = await isPluginActive2(db2, "tinymce-plugin");
|
|
6053
6311
|
let tinymceSettings;
|
|
6054
6312
|
if (tinymceEnabled) {
|
|
6055
|
-
const pluginService = new PluginService(
|
|
6313
|
+
const pluginService = new PluginService(db2);
|
|
6056
6314
|
const tinymcePlugin2 = await pluginService.getPlugin("tinymce-plugin");
|
|
6057
6315
|
tinymceSettings = tinymcePlugin2?.settings;
|
|
6058
6316
|
}
|
|
6059
|
-
const quillEnabled = await isPluginActive2(
|
|
6317
|
+
const quillEnabled = await isPluginActive2(db2, "quill-editor");
|
|
6060
6318
|
let quillSettings;
|
|
6061
6319
|
if (quillEnabled) {
|
|
6062
|
-
const pluginService = new PluginService(
|
|
6320
|
+
const pluginService = new PluginService(db2);
|
|
6063
6321
|
const quillPlugin = await pluginService.getPlugin("quill-editor");
|
|
6064
6322
|
quillSettings = quillPlugin?.settings;
|
|
6065
6323
|
}
|
|
6066
|
-
const mdxeditorEnabled = await isPluginActive2(
|
|
6324
|
+
const mdxeditorEnabled = await isPluginActive2(db2, "easy-mdx");
|
|
6067
6325
|
let mdxeditorSettings;
|
|
6068
6326
|
if (mdxeditorEnabled) {
|
|
6069
|
-
const pluginService = new PluginService(
|
|
6070
|
-
const
|
|
6071
|
-
mdxeditorSettings =
|
|
6327
|
+
const pluginService = new PluginService(db2);
|
|
6328
|
+
const mdxeditorPlugin = await pluginService.getPlugin("easy-mdx");
|
|
6329
|
+
mdxeditorSettings = mdxeditorPlugin?.settings;
|
|
6072
6330
|
}
|
|
6073
6331
|
const formData = {
|
|
6074
6332
|
id: content.id,
|
|
@@ -6128,8 +6386,8 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6128
6386
|
</div>
|
|
6129
6387
|
`);
|
|
6130
6388
|
}
|
|
6131
|
-
const
|
|
6132
|
-
const collection = await getCollection(
|
|
6389
|
+
const db2 = c.env.DB;
|
|
6390
|
+
const collection = await getCollection(db2, collectionId);
|
|
6133
6391
|
if (!collection) {
|
|
6134
6392
|
return c.html(html`
|
|
6135
6393
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -6137,7 +6395,7 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6137
6395
|
</div>
|
|
6138
6396
|
`);
|
|
6139
6397
|
}
|
|
6140
|
-
const fields = await getCollectionFields(
|
|
6398
|
+
const fields = await getCollectionFields(db2, collectionId);
|
|
6141
6399
|
const data = {};
|
|
6142
6400
|
const errors = {};
|
|
6143
6401
|
for (const field of fields) {
|
|
@@ -6195,13 +6453,13 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6195
6453
|
const scheduledUnpublishAt = formData.get("scheduled_unpublish_at");
|
|
6196
6454
|
const contentId = crypto.randomUUID();
|
|
6197
6455
|
const now = Date.now();
|
|
6198
|
-
const insertStmt =
|
|
6456
|
+
const insertStmt = db2.prepare(`
|
|
6199
6457
|
INSERT INTO content (
|
|
6200
6458
|
id, collection_id, slug, title, data, status,
|
|
6201
6459
|
scheduled_publish_at, scheduled_unpublish_at,
|
|
6202
|
-
meta_title, meta_description, author_id, created_at, updated_at
|
|
6460
|
+
meta_title, meta_description, author_id, created_by, created_at, updated_at
|
|
6203
6461
|
)
|
|
6204
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6462
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
6205
6463
|
`);
|
|
6206
6464
|
await insertStmt.bind(
|
|
6207
6465
|
contentId,
|
|
@@ -6215,12 +6473,13 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6215
6473
|
data.meta_title || null,
|
|
6216
6474
|
data.meta_description || null,
|
|
6217
6475
|
user?.userId || "unknown",
|
|
6476
|
+
user?.userId || "unknown",
|
|
6218
6477
|
now,
|
|
6219
6478
|
now
|
|
6220
6479
|
).run();
|
|
6221
6480
|
const cache = getCacheService(CACHE_CONFIGS.content);
|
|
6222
6481
|
await cache.invalidate(`content:list:${collectionId}:*`);
|
|
6223
|
-
const versionStmt =
|
|
6482
|
+
const versionStmt = db2.prepare(`
|
|
6224
6483
|
INSERT INTO content_versions (id, content_id, version, data, author_id, created_at)
|
|
6225
6484
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
6226
6485
|
`);
|
|
@@ -6232,7 +6491,7 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
6232
6491
|
user?.userId || "unknown",
|
|
6233
6492
|
now
|
|
6234
6493
|
).run();
|
|
6235
|
-
const workflowStmt =
|
|
6494
|
+
const workflowStmt = db2.prepare(`
|
|
6236
6495
|
INSERT INTO workflow_history (id, content_id, action, from_status, to_status, user_id, created_at)
|
|
6237
6496
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6238
6497
|
`);
|
|
@@ -6270,8 +6529,8 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6270
6529
|
const user = c.get("user");
|
|
6271
6530
|
const formData = await c.req.formData();
|
|
6272
6531
|
const action = formData.get("action");
|
|
6273
|
-
const
|
|
6274
|
-
const contentStmt =
|
|
6532
|
+
const db2 = c.env.DB;
|
|
6533
|
+
const contentStmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
6275
6534
|
const existingContent = await contentStmt.bind(id).first();
|
|
6276
6535
|
if (!existingContent) {
|
|
6277
6536
|
return c.html(html`
|
|
@@ -6280,7 +6539,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6280
6539
|
</div>
|
|
6281
6540
|
`);
|
|
6282
6541
|
}
|
|
6283
|
-
const collection = await getCollection(
|
|
6542
|
+
const collection = await getCollection(db2, existingContent.collection_id);
|
|
6284
6543
|
if (!collection) {
|
|
6285
6544
|
return c.html(html`
|
|
6286
6545
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
@@ -6288,7 +6547,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6288
6547
|
</div>
|
|
6289
6548
|
`);
|
|
6290
6549
|
}
|
|
6291
|
-
const fields = await getCollectionFields(
|
|
6550
|
+
const fields = await getCollectionFields(db2, existingContent.collection_id);
|
|
6292
6551
|
const data = {};
|
|
6293
6552
|
const errors = {};
|
|
6294
6553
|
for (const field of fields) {
|
|
@@ -6347,7 +6606,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6347
6606
|
const scheduledPublishAt = formData.get("scheduled_publish_at");
|
|
6348
6607
|
const scheduledUnpublishAt = formData.get("scheduled_unpublish_at");
|
|
6349
6608
|
const now = Date.now();
|
|
6350
|
-
const updateStmt =
|
|
6609
|
+
const updateStmt = db2.prepare(`
|
|
6351
6610
|
UPDATE content SET
|
|
6352
6611
|
slug = ?, title = ?, data = ?, status = ?,
|
|
6353
6612
|
scheduled_publish_at = ?, scheduled_unpublish_at = ?,
|
|
@@ -6371,10 +6630,10 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6371
6630
|
await cache.invalidate(`content:list:${existingContent.collection_id}:*`);
|
|
6372
6631
|
const existingData = JSON.parse(existingContent.data || "{}");
|
|
6373
6632
|
if (JSON.stringify(existingData) !== JSON.stringify(data)) {
|
|
6374
|
-
const versionCountStmt =
|
|
6633
|
+
const versionCountStmt = db2.prepare("SELECT MAX(version) as max_version FROM content_versions WHERE content_id = ?");
|
|
6375
6634
|
const versionResult = await versionCountStmt.bind(id).first();
|
|
6376
6635
|
const nextVersion = (versionResult?.max_version || 0) + 1;
|
|
6377
|
-
const versionStmt =
|
|
6636
|
+
const versionStmt = db2.prepare(`
|
|
6378
6637
|
INSERT INTO content_versions (id, content_id, version, data, author_id, created_at)
|
|
6379
6638
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
6380
6639
|
`);
|
|
@@ -6388,7 +6647,7 @@ adminContentRoutes.put("/:id", async (c) => {
|
|
|
6388
6647
|
).run();
|
|
6389
6648
|
}
|
|
6390
6649
|
if (status !== existingContent.status) {
|
|
6391
|
-
const workflowStmt =
|
|
6650
|
+
const workflowStmt = db2.prepare(`
|
|
6392
6651
|
INSERT INTO workflow_history (id, content_id, action, from_status, to_status, user_id, created_at)
|
|
6393
6652
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6394
6653
|
`);
|
|
@@ -6425,12 +6684,12 @@ adminContentRoutes.post("/preview", async (c) => {
|
|
|
6425
6684
|
try {
|
|
6426
6685
|
const formData = await c.req.formData();
|
|
6427
6686
|
const collectionId = formData.get("collection_id");
|
|
6428
|
-
const
|
|
6429
|
-
const collection = await getCollection(
|
|
6687
|
+
const db2 = c.env.DB;
|
|
6688
|
+
const collection = await getCollection(db2, collectionId);
|
|
6430
6689
|
if (!collection) {
|
|
6431
6690
|
return c.html("<p>Collection not found</p>");
|
|
6432
6691
|
}
|
|
6433
|
-
const fields = await getCollectionFields(
|
|
6692
|
+
const fields = await getCollectionFields(db2, collectionId);
|
|
6434
6693
|
const data = {};
|
|
6435
6694
|
for (const field of fields) {
|
|
6436
6695
|
const value = formData.get(field.field_name);
|
|
@@ -6504,8 +6763,8 @@ adminContentRoutes.post("/duplicate", async (c) => {
|
|
|
6504
6763
|
if (!originalId) {
|
|
6505
6764
|
return c.json({ success: false, error: "Content ID required" });
|
|
6506
6765
|
}
|
|
6507
|
-
const
|
|
6508
|
-
const contentStmt =
|
|
6766
|
+
const db2 = c.env.DB;
|
|
6767
|
+
const contentStmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
6509
6768
|
const original = await contentStmt.bind(originalId).first();
|
|
6510
6769
|
if (!original) {
|
|
6511
6770
|
return c.json({ success: false, error: "Content not found" });
|
|
@@ -6514,7 +6773,7 @@ adminContentRoutes.post("/duplicate", async (c) => {
|
|
|
6514
6773
|
const now = Date.now();
|
|
6515
6774
|
const originalData = JSON.parse(original.data || "{}");
|
|
6516
6775
|
originalData.title = `${originalData.title || "Untitled"} (Copy)`;
|
|
6517
|
-
const insertStmt =
|
|
6776
|
+
const insertStmt = db2.prepare(`
|
|
6518
6777
|
INSERT INTO content (
|
|
6519
6778
|
id, collection_id, slug, title, data, status,
|
|
6520
6779
|
author_id, created_at, updated_at
|
|
@@ -6637,11 +6896,11 @@ adminContentRoutes.post("/bulk-action", async (c) => {
|
|
|
6637
6896
|
if (!action || !ids || ids.length === 0) {
|
|
6638
6897
|
return c.json({ success: false, error: "Action and IDs required" });
|
|
6639
6898
|
}
|
|
6640
|
-
const
|
|
6899
|
+
const db2 = c.env.DB;
|
|
6641
6900
|
const now = Date.now();
|
|
6642
6901
|
if (action === "delete") {
|
|
6643
6902
|
const placeholders = ids.map(() => "?").join(",");
|
|
6644
|
-
const stmt =
|
|
6903
|
+
const stmt = db2.prepare(`
|
|
6645
6904
|
UPDATE content
|
|
6646
6905
|
SET status = 'deleted', updated_at = ?
|
|
6647
6906
|
WHERE id IN (${placeholders})
|
|
@@ -6650,7 +6909,7 @@ adminContentRoutes.post("/bulk-action", async (c) => {
|
|
|
6650
6909
|
} else if (action === "publish" || action === "draft") {
|
|
6651
6910
|
const placeholders = ids.map(() => "?").join(",");
|
|
6652
6911
|
const publishedAt = action === "publish" ? now : null;
|
|
6653
|
-
const stmt =
|
|
6912
|
+
const stmt = db2.prepare(`
|
|
6654
6913
|
UPDATE content
|
|
6655
6914
|
SET status = ?, published_at = ?, updated_at = ?
|
|
6656
6915
|
WHERE id IN (${placeholders})
|
|
@@ -6673,15 +6932,15 @@ adminContentRoutes.post("/bulk-action", async (c) => {
|
|
|
6673
6932
|
adminContentRoutes.delete("/:id", async (c) => {
|
|
6674
6933
|
try {
|
|
6675
6934
|
const id = c.req.param("id");
|
|
6676
|
-
const
|
|
6935
|
+
const db2 = c.env.DB;
|
|
6677
6936
|
const user = c.get("user");
|
|
6678
|
-
const contentStmt =
|
|
6937
|
+
const contentStmt = db2.prepare("SELECT id, title FROM content WHERE id = ?");
|
|
6679
6938
|
const content = await contentStmt.bind(id).first();
|
|
6680
6939
|
if (!content) {
|
|
6681
6940
|
return c.json({ success: false, error: "Content not found" }, 404);
|
|
6682
6941
|
}
|
|
6683
6942
|
const now = Date.now();
|
|
6684
|
-
const deleteStmt =
|
|
6943
|
+
const deleteStmt = db2.prepare(`
|
|
6685
6944
|
UPDATE content
|
|
6686
6945
|
SET status = 'deleted', updated_at = ?
|
|
6687
6946
|
WHERE id = ?
|
|
@@ -6710,13 +6969,13 @@ adminContentRoutes.delete("/:id", async (c) => {
|
|
|
6710
6969
|
adminContentRoutes.get("/:id/versions", async (c) => {
|
|
6711
6970
|
try {
|
|
6712
6971
|
const id = c.req.param("id");
|
|
6713
|
-
const
|
|
6714
|
-
const contentStmt =
|
|
6972
|
+
const db2 = c.env.DB;
|
|
6973
|
+
const contentStmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
6715
6974
|
const content = await contentStmt.bind(id).first();
|
|
6716
6975
|
if (!content) {
|
|
6717
6976
|
return c.html("<p>Content not found</p>");
|
|
6718
6977
|
}
|
|
6719
|
-
const versionsStmt =
|
|
6978
|
+
const versionsStmt = db2.prepare(`
|
|
6720
6979
|
SELECT cv.*, u.first_name, u.last_name, u.email
|
|
6721
6980
|
FROM content_versions cv
|
|
6722
6981
|
LEFT JOIN users u ON cv.author_id = u.id
|
|
@@ -6753,8 +7012,8 @@ adminContentRoutes.post("/:id/restore/:version", async (c) => {
|
|
|
6753
7012
|
const id = c.req.param("id");
|
|
6754
7013
|
const version = parseInt(c.req.param("version"));
|
|
6755
7014
|
const user = c.get("user");
|
|
6756
|
-
const
|
|
6757
|
-
const versionStmt =
|
|
7015
|
+
const db2 = c.env.DB;
|
|
7016
|
+
const versionStmt = db2.prepare(`
|
|
6758
7017
|
SELECT * FROM content_versions
|
|
6759
7018
|
WHERE content_id = ? AND version = ?
|
|
6760
7019
|
`);
|
|
@@ -6762,14 +7021,14 @@ adminContentRoutes.post("/:id/restore/:version", async (c) => {
|
|
|
6762
7021
|
if (!versionData) {
|
|
6763
7022
|
return c.json({ success: false, error: "Version not found" });
|
|
6764
7023
|
}
|
|
6765
|
-
const contentStmt =
|
|
7024
|
+
const contentStmt = db2.prepare("SELECT * FROM content WHERE id = ?");
|
|
6766
7025
|
const currentContent = await contentStmt.bind(id).first();
|
|
6767
7026
|
if (!currentContent) {
|
|
6768
7027
|
return c.json({ success: false, error: "Content not found" });
|
|
6769
7028
|
}
|
|
6770
7029
|
const restoredData = JSON.parse(versionData.data);
|
|
6771
7030
|
const now = Date.now();
|
|
6772
|
-
const updateStmt =
|
|
7031
|
+
const updateStmt = db2.prepare(`
|
|
6773
7032
|
UPDATE content SET
|
|
6774
7033
|
title = ?, data = ?, updated_at = ?
|
|
6775
7034
|
WHERE id = ?
|
|
@@ -6780,10 +7039,10 @@ adminContentRoutes.post("/:id/restore/:version", async (c) => {
|
|
|
6780
7039
|
now,
|
|
6781
7040
|
id
|
|
6782
7041
|
).run();
|
|
6783
|
-
const nextVersionStmt =
|
|
7042
|
+
const nextVersionStmt = db2.prepare("SELECT MAX(version) as max_version FROM content_versions WHERE content_id = ?");
|
|
6784
7043
|
const nextVersionResult = await nextVersionStmt.bind(id).first();
|
|
6785
7044
|
const nextVersion = (nextVersionResult?.max_version || 0) + 1;
|
|
6786
|
-
const newVersionStmt =
|
|
7045
|
+
const newVersionStmt = db2.prepare(`
|
|
6787
7046
|
INSERT INTO content_versions (id, content_id, version, data, author_id, created_at)
|
|
6788
7047
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
6789
7048
|
`);
|
|
@@ -6795,7 +7054,7 @@ adminContentRoutes.post("/:id/restore/:version", async (c) => {
|
|
|
6795
7054
|
user?.userId || "unknown",
|
|
6796
7055
|
now
|
|
6797
7056
|
).run();
|
|
6798
|
-
const workflowStmt =
|
|
7057
|
+
const workflowStmt = db2.prepare(`
|
|
6799
7058
|
INSERT INTO workflow_history (id, content_id, action, from_status, to_status, user_id, comment, created_at)
|
|
6800
7059
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6801
7060
|
`);
|
|
@@ -6819,8 +7078,8 @@ adminContentRoutes.get("/:id/version/:version/preview", async (c) => {
|
|
|
6819
7078
|
try {
|
|
6820
7079
|
const id = c.req.param("id");
|
|
6821
7080
|
const version = parseInt(c.req.param("version"));
|
|
6822
|
-
const
|
|
6823
|
-
const versionStmt =
|
|
7081
|
+
const db2 = c.env.DB;
|
|
7082
|
+
const versionStmt = db2.prepare(`
|
|
6824
7083
|
SELECT cv.*, c.collection_id, col.display_name as collection_name
|
|
6825
7084
|
FROM content_versions cv
|
|
6826
7085
|
JOIN content c ON cv.content_id = c.id
|
|
@@ -8741,9 +9000,9 @@ var ROLES = [
|
|
|
8741
9000
|
];
|
|
8742
9001
|
userRoutes.get("/profile", async (c) => {
|
|
8743
9002
|
const user = c.get("user");
|
|
8744
|
-
const
|
|
9003
|
+
const db2 = c.env.DB;
|
|
8745
9004
|
try {
|
|
8746
|
-
const userStmt =
|
|
9005
|
+
const userStmt = db2.prepare(`
|
|
8747
9006
|
SELECT id, email, username, first_name, last_name, phone, bio, avatar_url,
|
|
8748
9007
|
timezone, language, theme, email_notifications, two_factor_enabled,
|
|
8749
9008
|
role, created_at, last_login_at
|
|
@@ -8801,7 +9060,7 @@ userRoutes.get("/profile", async (c) => {
|
|
|
8801
9060
|
});
|
|
8802
9061
|
userRoutes.put("/profile", async (c) => {
|
|
8803
9062
|
const user = c.get("user");
|
|
8804
|
-
const
|
|
9063
|
+
const db2 = c.env.DB;
|
|
8805
9064
|
try {
|
|
8806
9065
|
const formData = await c.req.formData();
|
|
8807
9066
|
const firstName = sanitizeInput(formData.get("first_name")?.toString());
|
|
@@ -8828,7 +9087,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
8828
9087
|
dismissible: true
|
|
8829
9088
|
}));
|
|
8830
9089
|
}
|
|
8831
|
-
const checkStmt =
|
|
9090
|
+
const checkStmt = db2.prepare(`
|
|
8832
9091
|
SELECT id FROM users
|
|
8833
9092
|
WHERE (username = ? OR email = ?) AND id != ? AND is_active = 1
|
|
8834
9093
|
`);
|
|
@@ -8840,7 +9099,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
8840
9099
|
dismissible: true
|
|
8841
9100
|
}));
|
|
8842
9101
|
}
|
|
8843
|
-
const updateStmt =
|
|
9102
|
+
const updateStmt = db2.prepare(`
|
|
8844
9103
|
UPDATE users SET
|
|
8845
9104
|
first_name = ?, last_name = ?, username = ?, email = ?,
|
|
8846
9105
|
phone = ?, bio = ?, timezone = ?, language = ?,
|
|
@@ -8861,7 +9120,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
8861
9120
|
user.userId
|
|
8862
9121
|
).run();
|
|
8863
9122
|
await logActivity(
|
|
8864
|
-
|
|
9123
|
+
db2,
|
|
8865
9124
|
user.userId,
|
|
8866
9125
|
"profile.update",
|
|
8867
9126
|
"users",
|
|
@@ -8886,7 +9145,7 @@ userRoutes.put("/profile", async (c) => {
|
|
|
8886
9145
|
});
|
|
8887
9146
|
userRoutes.post("/profile/avatar", async (c) => {
|
|
8888
9147
|
const user = c.get("user");
|
|
8889
|
-
const
|
|
9148
|
+
const db2 = c.env.DB;
|
|
8890
9149
|
try {
|
|
8891
9150
|
const formData = await c.req.formData();
|
|
8892
9151
|
const avatarFile = formData.get("avatar");
|
|
@@ -8914,17 +9173,17 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
8914
9173
|
}));
|
|
8915
9174
|
}
|
|
8916
9175
|
const avatarUrl = `/uploads/avatars/${user.userId}-${Date.now()}.${avatarFile.type.split("/")[1]}`;
|
|
8917
|
-
const updateStmt =
|
|
9176
|
+
const updateStmt = db2.prepare(`
|
|
8918
9177
|
UPDATE users SET avatar_url = ?, updated_at = ?
|
|
8919
9178
|
WHERE id = ?
|
|
8920
9179
|
`);
|
|
8921
9180
|
await updateStmt.bind(avatarUrl, Date.now(), user.userId).run();
|
|
8922
|
-
const userStmt =
|
|
9181
|
+
const userStmt = db2.prepare(`
|
|
8923
9182
|
SELECT first_name, last_name FROM users WHERE id = ?
|
|
8924
9183
|
`);
|
|
8925
9184
|
const userData = await userStmt.bind(user.userId).first();
|
|
8926
9185
|
await logActivity(
|
|
8927
|
-
|
|
9186
|
+
db2,
|
|
8928
9187
|
user.userId,
|
|
8929
9188
|
"profile.avatar_update",
|
|
8930
9189
|
"users",
|
|
@@ -8956,7 +9215,7 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
8956
9215
|
});
|
|
8957
9216
|
userRoutes.post("/profile/password", async (c) => {
|
|
8958
9217
|
const user = c.get("user");
|
|
8959
|
-
const
|
|
9218
|
+
const db2 = c.env.DB;
|
|
8960
9219
|
try {
|
|
8961
9220
|
const formData = await c.req.formData();
|
|
8962
9221
|
const currentPassword = formData.get("current_password")?.toString() || "";
|
|
@@ -8983,7 +9242,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
8983
9242
|
dismissible: true
|
|
8984
9243
|
}));
|
|
8985
9244
|
}
|
|
8986
|
-
const userStmt =
|
|
9245
|
+
const userStmt = db2.prepare(`
|
|
8987
9246
|
SELECT password_hash FROM users WHERE id = ? AND is_active = 1
|
|
8988
9247
|
`);
|
|
8989
9248
|
const userData = await userStmt.bind(user.userId).first();
|
|
@@ -9003,7 +9262,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9003
9262
|
}));
|
|
9004
9263
|
}
|
|
9005
9264
|
const newPasswordHash = await AuthManager.hashPassword(newPassword);
|
|
9006
|
-
const historyStmt =
|
|
9265
|
+
const historyStmt = db2.prepare(`
|
|
9007
9266
|
INSERT INTO password_history (id, user_id, password_hash, created_at)
|
|
9008
9267
|
VALUES (?, ?, ?, ?)
|
|
9009
9268
|
`);
|
|
@@ -9013,13 +9272,13 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9013
9272
|
userData.password_hash,
|
|
9014
9273
|
Date.now()
|
|
9015
9274
|
).run();
|
|
9016
|
-
const updateStmt =
|
|
9275
|
+
const updateStmt = db2.prepare(`
|
|
9017
9276
|
UPDATE users SET password_hash = ?, updated_at = ?
|
|
9018
9277
|
WHERE id = ?
|
|
9019
9278
|
`);
|
|
9020
9279
|
await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
|
|
9021
9280
|
await logActivity(
|
|
9022
|
-
|
|
9281
|
+
db2,
|
|
9023
9282
|
user.userId,
|
|
9024
9283
|
"profile.password_change",
|
|
9025
9284
|
"users",
|
|
@@ -9043,7 +9302,7 @@ userRoutes.post("/profile/password", async (c) => {
|
|
|
9043
9302
|
}
|
|
9044
9303
|
});
|
|
9045
9304
|
userRoutes.get("/users", async (c) => {
|
|
9046
|
-
const
|
|
9305
|
+
const db2 = c.env.DB;
|
|
9047
9306
|
const user = c.get("user");
|
|
9048
9307
|
try {
|
|
9049
9308
|
const page = parseInt(c.req.query("page") || "1");
|
|
@@ -9070,7 +9329,7 @@ userRoutes.get("/users", async (c) => {
|
|
|
9070
9329
|
whereClause += " AND u.role = ?";
|
|
9071
9330
|
params.push(roleFilter);
|
|
9072
9331
|
}
|
|
9073
|
-
const usersStmt =
|
|
9332
|
+
const usersStmt = db2.prepare(`
|
|
9074
9333
|
SELECT u.id, u.email, u.username, u.first_name, u.last_name,
|
|
9075
9334
|
u.role, u.avatar_url, u.created_at, u.last_login_at, u.updated_at,
|
|
9076
9335
|
u.email_verified, u.two_factor_enabled, u.is_active
|
|
@@ -9080,13 +9339,13 @@ userRoutes.get("/users", async (c) => {
|
|
|
9080
9339
|
LIMIT ? OFFSET ?
|
|
9081
9340
|
`);
|
|
9082
9341
|
const { results: usersData } = await usersStmt.bind(...params, limit, offset).all();
|
|
9083
|
-
const countStmt =
|
|
9342
|
+
const countStmt = db2.prepare(`
|
|
9084
9343
|
SELECT COUNT(*) as total FROM users u ${whereClause}
|
|
9085
9344
|
`);
|
|
9086
9345
|
const countResult = await countStmt.bind(...params).first();
|
|
9087
9346
|
const totalUsers = countResult?.total || 0;
|
|
9088
9347
|
await logActivity(
|
|
9089
|
-
|
|
9348
|
+
db2,
|
|
9090
9349
|
user.userId,
|
|
9091
9350
|
"users.list_view",
|
|
9092
9351
|
"users",
|
|
@@ -9183,7 +9442,7 @@ userRoutes.get("/users/new", async (c) => {
|
|
|
9183
9442
|
}
|
|
9184
9443
|
});
|
|
9185
9444
|
userRoutes.post("/users/new", async (c) => {
|
|
9186
|
-
const
|
|
9445
|
+
const db2 = c.env.DB;
|
|
9187
9446
|
const user = c.get("user");
|
|
9188
9447
|
try {
|
|
9189
9448
|
const formData = await c.req.formData();
|
|
@@ -9227,7 +9486,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9227
9486
|
dismissible: true
|
|
9228
9487
|
}));
|
|
9229
9488
|
}
|
|
9230
|
-
const checkStmt =
|
|
9489
|
+
const checkStmt = db2.prepare(`
|
|
9231
9490
|
SELECT id FROM users
|
|
9232
9491
|
WHERE username = ? OR email = ?
|
|
9233
9492
|
`);
|
|
@@ -9241,7 +9500,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9241
9500
|
}
|
|
9242
9501
|
const passwordHash = await AuthManager.hashPassword(password);
|
|
9243
9502
|
const userId = crypto.randomUUID();
|
|
9244
|
-
const createStmt =
|
|
9503
|
+
const createStmt = db2.prepare(`
|
|
9245
9504
|
INSERT INTO users (
|
|
9246
9505
|
id, email, username, first_name, last_name, phone, bio,
|
|
9247
9506
|
password_hash, role, is_active, email_verified, created_at, updated_at
|
|
@@ -9263,7 +9522,7 @@ userRoutes.post("/users/new", async (c) => {
|
|
|
9263
9522
|
Date.now()
|
|
9264
9523
|
).run();
|
|
9265
9524
|
await logActivity(
|
|
9266
|
-
|
|
9525
|
+
db2,
|
|
9267
9526
|
user.userId,
|
|
9268
9527
|
"user!.create",
|
|
9269
9528
|
"users",
|
|
@@ -9286,11 +9545,11 @@ userRoutes.get("/users/:id", async (c) => {
|
|
|
9286
9545
|
if (c.req.path.endsWith("/edit")) {
|
|
9287
9546
|
return c.notFound();
|
|
9288
9547
|
}
|
|
9289
|
-
const
|
|
9548
|
+
const db2 = c.env.DB;
|
|
9290
9549
|
const user = c.get("user");
|
|
9291
9550
|
const userId = c.req.param("id");
|
|
9292
9551
|
try {
|
|
9293
|
-
const userStmt =
|
|
9552
|
+
const userStmt = db2.prepare(`
|
|
9294
9553
|
SELECT id, email, username, first_name, last_name, phone, bio, avatar_url,
|
|
9295
9554
|
role, is_active, email_verified, two_factor_enabled, created_at, last_login_at
|
|
9296
9555
|
FROM users
|
|
@@ -9301,7 +9560,7 @@ userRoutes.get("/users/:id", async (c) => {
|
|
|
9301
9560
|
return c.json({ error: "User not found" }, 404);
|
|
9302
9561
|
}
|
|
9303
9562
|
await logActivity(
|
|
9304
|
-
|
|
9563
|
+
db2,
|
|
9305
9564
|
user.userId,
|
|
9306
9565
|
"user!.view",
|
|
9307
9566
|
"users",
|
|
@@ -9334,11 +9593,11 @@ userRoutes.get("/users/:id", async (c) => {
|
|
|
9334
9593
|
}
|
|
9335
9594
|
});
|
|
9336
9595
|
userRoutes.get("/users/:id/edit", async (c) => {
|
|
9337
|
-
const
|
|
9596
|
+
const db2 = c.env.DB;
|
|
9338
9597
|
const user = c.get("user");
|
|
9339
9598
|
const userId = c.req.param("id");
|
|
9340
9599
|
try {
|
|
9341
|
-
const userStmt =
|
|
9600
|
+
const userStmt = db2.prepare(`
|
|
9342
9601
|
SELECT id, email, username, first_name, last_name, phone, bio, avatar_url,
|
|
9343
9602
|
role, is_active, email_verified, two_factor_enabled, created_at, last_login_at
|
|
9344
9603
|
FROM users
|
|
@@ -9388,7 +9647,7 @@ userRoutes.get("/users/:id/edit", async (c) => {
|
|
|
9388
9647
|
}
|
|
9389
9648
|
});
|
|
9390
9649
|
userRoutes.put("/users/:id", async (c) => {
|
|
9391
|
-
const
|
|
9650
|
+
const db2 = c.env.DB;
|
|
9392
9651
|
const user = c.get("user");
|
|
9393
9652
|
const userId = c.req.param("id");
|
|
9394
9653
|
try {
|
|
@@ -9417,7 +9676,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9417
9676
|
dismissible: true
|
|
9418
9677
|
}));
|
|
9419
9678
|
}
|
|
9420
|
-
const checkStmt =
|
|
9679
|
+
const checkStmt = db2.prepare(`
|
|
9421
9680
|
SELECT id FROM users
|
|
9422
9681
|
WHERE (username = ? OR email = ?) AND id != ?
|
|
9423
9682
|
`);
|
|
@@ -9429,7 +9688,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9429
9688
|
dismissible: true
|
|
9430
9689
|
}));
|
|
9431
9690
|
}
|
|
9432
|
-
const updateStmt =
|
|
9691
|
+
const updateStmt = db2.prepare(`
|
|
9433
9692
|
UPDATE users SET
|
|
9434
9693
|
first_name = ?, last_name = ?, username = ?, email = ?,
|
|
9435
9694
|
phone = ?, bio = ?, role = ?, is_active = ?, email_verified = ?,
|
|
@@ -9450,7 +9709,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9450
9709
|
userId
|
|
9451
9710
|
).run();
|
|
9452
9711
|
await logActivity(
|
|
9453
|
-
|
|
9712
|
+
db2,
|
|
9454
9713
|
user.userId,
|
|
9455
9714
|
"user!.update",
|
|
9456
9715
|
"users",
|
|
@@ -9474,7 +9733,7 @@ userRoutes.put("/users/:id", async (c) => {
|
|
|
9474
9733
|
}
|
|
9475
9734
|
});
|
|
9476
9735
|
userRoutes.post("/users/:id/toggle", async (c) => {
|
|
9477
|
-
const
|
|
9736
|
+
const db2 = c.env.DB;
|
|
9478
9737
|
const user = c.get("user");
|
|
9479
9738
|
const userId = c.req.param("id");
|
|
9480
9739
|
try {
|
|
@@ -9483,19 +9742,19 @@ userRoutes.post("/users/:id/toggle", async (c) => {
|
|
|
9483
9742
|
if (userId === user.userId && !active) {
|
|
9484
9743
|
return c.json({ error: "You cannot deactivate your own account" }, 400);
|
|
9485
9744
|
}
|
|
9486
|
-
const userStmt =
|
|
9745
|
+
const userStmt = db2.prepare(`
|
|
9487
9746
|
SELECT id, email FROM users WHERE id = ?
|
|
9488
9747
|
`);
|
|
9489
9748
|
const userToToggle = await userStmt.bind(userId).first();
|
|
9490
9749
|
if (!userToToggle) {
|
|
9491
9750
|
return c.json({ error: "User not found" }, 404);
|
|
9492
9751
|
}
|
|
9493
|
-
const toggleStmt =
|
|
9752
|
+
const toggleStmt = db2.prepare(`
|
|
9494
9753
|
UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
|
|
9495
9754
|
`);
|
|
9496
9755
|
await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
|
|
9497
9756
|
await logActivity(
|
|
9498
|
-
|
|
9757
|
+
db2,
|
|
9499
9758
|
user.userId,
|
|
9500
9759
|
active ? "user.activate" : "user.deactivate",
|
|
9501
9760
|
"users",
|
|
@@ -9514,7 +9773,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
|
|
|
9514
9773
|
}
|
|
9515
9774
|
});
|
|
9516
9775
|
userRoutes.delete("/users/:id", async (c) => {
|
|
9517
|
-
const
|
|
9776
|
+
const db2 = c.env.DB;
|
|
9518
9777
|
const user = c.get("user");
|
|
9519
9778
|
const userId = c.req.param("id");
|
|
9520
9779
|
try {
|
|
@@ -9523,7 +9782,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
9523
9782
|
if (userId === user.userId) {
|
|
9524
9783
|
return c.json({ error: "You cannot delete your own account" }, 400);
|
|
9525
9784
|
}
|
|
9526
|
-
const userStmt =
|
|
9785
|
+
const userStmt = db2.prepare(`
|
|
9527
9786
|
SELECT id, email FROM users WHERE id = ?
|
|
9528
9787
|
`);
|
|
9529
9788
|
const userToDelete = await userStmt.bind(userId).first();
|
|
@@ -9531,12 +9790,12 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
9531
9790
|
return c.json({ error: "User not found" }, 404);
|
|
9532
9791
|
}
|
|
9533
9792
|
if (hardDelete) {
|
|
9534
|
-
const deleteStmt =
|
|
9793
|
+
const deleteStmt = db2.prepare(`
|
|
9535
9794
|
DELETE FROM users WHERE id = ?
|
|
9536
9795
|
`);
|
|
9537
9796
|
await deleteStmt.bind(userId).run();
|
|
9538
9797
|
await logActivity(
|
|
9539
|
-
|
|
9798
|
+
db2,
|
|
9540
9799
|
user.userId,
|
|
9541
9800
|
"user!.hard_delete",
|
|
9542
9801
|
"users",
|
|
@@ -9550,12 +9809,12 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
9550
9809
|
message: "User permanently deleted"
|
|
9551
9810
|
});
|
|
9552
9811
|
} else {
|
|
9553
|
-
const deleteStmt =
|
|
9812
|
+
const deleteStmt = db2.prepare(`
|
|
9554
9813
|
UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
|
|
9555
9814
|
`);
|
|
9556
9815
|
await deleteStmt.bind(Date.now(), userId).run();
|
|
9557
9816
|
await logActivity(
|
|
9558
|
-
|
|
9817
|
+
db2,
|
|
9559
9818
|
user.userId,
|
|
9560
9819
|
"user!.soft_delete",
|
|
9561
9820
|
"users",
|
|
@@ -9575,7 +9834,7 @@ userRoutes.delete("/users/:id", async (c) => {
|
|
|
9575
9834
|
}
|
|
9576
9835
|
});
|
|
9577
9836
|
userRoutes.post("/invite-user", async (c) => {
|
|
9578
|
-
const
|
|
9837
|
+
const db2 = c.env.DB;
|
|
9579
9838
|
const user = c.get("user");
|
|
9580
9839
|
try {
|
|
9581
9840
|
const formData = await c.req.formData();
|
|
@@ -9590,7 +9849,7 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
9590
9849
|
if (!emailRegex.test(email)) {
|
|
9591
9850
|
return c.json({ error: "Please enter a valid email address" }, 400);
|
|
9592
9851
|
}
|
|
9593
|
-
const existingUserStmt =
|
|
9852
|
+
const existingUserStmt = db2.prepare(`
|
|
9594
9853
|
SELECT id FROM users WHERE email = ?
|
|
9595
9854
|
`);
|
|
9596
9855
|
const existingUser = await existingUserStmt.bind(email).first();
|
|
@@ -9599,7 +9858,7 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
9599
9858
|
}
|
|
9600
9859
|
const invitationToken = crypto.randomUUID();
|
|
9601
9860
|
const userId = crypto.randomUUID();
|
|
9602
|
-
const createUserStmt =
|
|
9861
|
+
const createUserStmt = db2.prepare(`
|
|
9603
9862
|
INSERT INTO users (
|
|
9604
9863
|
id, email, first_name, last_name, role,
|
|
9605
9864
|
invitation_token, invited_by, invited_at,
|
|
@@ -9621,7 +9880,7 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
9621
9880
|
Date.now()
|
|
9622
9881
|
).run();
|
|
9623
9882
|
await logActivity(
|
|
9624
|
-
|
|
9883
|
+
db2,
|
|
9625
9884
|
user.userId,
|
|
9626
9885
|
"user!.invite_sent",
|
|
9627
9886
|
"users",
|
|
@@ -9650,11 +9909,11 @@ userRoutes.post("/invite-user", async (c) => {
|
|
|
9650
9909
|
}
|
|
9651
9910
|
});
|
|
9652
9911
|
userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
9653
|
-
const
|
|
9912
|
+
const db2 = c.env.DB;
|
|
9654
9913
|
const user = c.get("user");
|
|
9655
9914
|
const userId = c.req.param("id");
|
|
9656
9915
|
try {
|
|
9657
|
-
const userStmt =
|
|
9916
|
+
const userStmt = db2.prepare(`
|
|
9658
9917
|
SELECT id, email, first_name, last_name, role, invitation_token
|
|
9659
9918
|
FROM users
|
|
9660
9919
|
WHERE id = ? AND is_active = 0 AND invitation_token IS NOT NULL
|
|
@@ -9664,7 +9923,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
|
9664
9923
|
return c.json({ error: "User not found or invitation not valid" }, 404);
|
|
9665
9924
|
}
|
|
9666
9925
|
const newInvitationToken = crypto.randomUUID();
|
|
9667
|
-
const updateStmt =
|
|
9926
|
+
const updateStmt = db2.prepare(`
|
|
9668
9927
|
UPDATE users SET
|
|
9669
9928
|
invitation_token = ?,
|
|
9670
9929
|
invited_at = ?,
|
|
@@ -9678,7 +9937,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
|
9678
9937
|
userId
|
|
9679
9938
|
).run();
|
|
9680
9939
|
await logActivity(
|
|
9681
|
-
|
|
9940
|
+
db2,
|
|
9682
9941
|
user.userId,
|
|
9683
9942
|
"user!.invitation_resent",
|
|
9684
9943
|
"users",
|
|
@@ -9699,11 +9958,11 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
|
|
|
9699
9958
|
}
|
|
9700
9959
|
});
|
|
9701
9960
|
userRoutes.delete("/cancel-invitation/:id", async (c) => {
|
|
9702
|
-
const
|
|
9961
|
+
const db2 = c.env.DB;
|
|
9703
9962
|
const user = c.get("user");
|
|
9704
9963
|
const userId = c.req.param("id");
|
|
9705
9964
|
try {
|
|
9706
|
-
const userStmt =
|
|
9965
|
+
const userStmt = db2.prepare(`
|
|
9707
9966
|
SELECT id, email FROM users
|
|
9708
9967
|
WHERE id = ? AND is_active = 0 AND invitation_token IS NOT NULL
|
|
9709
9968
|
`);
|
|
@@ -9711,10 +9970,10 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
|
|
|
9711
9970
|
if (!invitedUser) {
|
|
9712
9971
|
return c.json({ error: "User not found or invitation not valid" }, 404);
|
|
9713
9972
|
}
|
|
9714
|
-
const deleteStmt =
|
|
9973
|
+
const deleteStmt = db2.prepare(`DELETE FROM users WHERE id = ?`);
|
|
9715
9974
|
await deleteStmt.bind(userId).run();
|
|
9716
9975
|
await logActivity(
|
|
9717
|
-
|
|
9976
|
+
db2,
|
|
9718
9977
|
user.userId,
|
|
9719
9978
|
"user!.invitation_cancelled",
|
|
9720
9979
|
"users",
|
|
@@ -9733,7 +9992,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
|
|
|
9733
9992
|
}
|
|
9734
9993
|
});
|
|
9735
9994
|
userRoutes.get("/activity-logs", async (c) => {
|
|
9736
|
-
const
|
|
9995
|
+
const db2 = c.env.DB;
|
|
9737
9996
|
const user = c.get("user");
|
|
9738
9997
|
try {
|
|
9739
9998
|
const page = parseInt(c.req.query("page") || "1");
|
|
@@ -9771,7 +10030,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
9771
10030
|
params.push(toTimestamp);
|
|
9772
10031
|
}
|
|
9773
10032
|
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
|
|
9774
|
-
const logsStmt =
|
|
10033
|
+
const logsStmt = db2.prepare(`
|
|
9775
10034
|
SELECT
|
|
9776
10035
|
al.id, al.user_id, al.action, al.resource_type, al.resource_id,
|
|
9777
10036
|
al.details, al.ip_address, al.user_agent, al.created_at,
|
|
@@ -9784,7 +10043,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
9784
10043
|
LIMIT ? OFFSET ?
|
|
9785
10044
|
`);
|
|
9786
10045
|
const { results: logs } = await logsStmt.bind(...params, limit, offset).all();
|
|
9787
|
-
const countStmt =
|
|
10046
|
+
const countStmt = db2.prepare(`
|
|
9788
10047
|
SELECT COUNT(*) as total
|
|
9789
10048
|
FROM activity_logs al
|
|
9790
10049
|
LEFT JOIN users u ON al.user_id = u.id
|
|
@@ -9797,7 +10056,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
9797
10056
|
details: log.details ? JSON.parse(log.details) : null
|
|
9798
10057
|
}));
|
|
9799
10058
|
await logActivity(
|
|
9800
|
-
|
|
10059
|
+
db2,
|
|
9801
10060
|
user.userId,
|
|
9802
10061
|
"activity.logs_viewed",
|
|
9803
10062
|
void 0,
|
|
@@ -9839,7 +10098,7 @@ userRoutes.get("/activity-logs", async (c) => {
|
|
|
9839
10098
|
}
|
|
9840
10099
|
});
|
|
9841
10100
|
userRoutes.get("/activity-logs/export", async (c) => {
|
|
9842
|
-
const
|
|
10101
|
+
const db2 = c.env.DB;
|
|
9843
10102
|
const user = c.get("user");
|
|
9844
10103
|
try {
|
|
9845
10104
|
const filters = {
|
|
@@ -9874,7 +10133,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
|
|
|
9874
10133
|
params.push(toTimestamp);
|
|
9875
10134
|
}
|
|
9876
10135
|
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
|
|
9877
|
-
const logsStmt =
|
|
10136
|
+
const logsStmt = db2.prepare(`
|
|
9878
10137
|
SELECT
|
|
9879
10138
|
al.id, al.user_id, al.action, al.resource_type, al.resource_id,
|
|
9880
10139
|
al.details, al.ip_address, al.user_agent, al.created_at,
|
|
@@ -9904,7 +10163,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
|
|
|
9904
10163
|
}
|
|
9905
10164
|
const csvContent = csvRows.join("\n");
|
|
9906
10165
|
await logActivity(
|
|
9907
|
-
|
|
10166
|
+
db2,
|
|
9908
10167
|
user.userId,
|
|
9909
10168
|
"activity.logs_exported",
|
|
9910
10169
|
void 0,
|
|
@@ -11254,7 +11513,7 @@ adminMediaRoutes.get("/", async (c) => {
|
|
|
11254
11513
|
const ____cacheBust = searchParams.get("t");
|
|
11255
11514
|
const limit = 24;
|
|
11256
11515
|
const offset = (page - 1) * limit;
|
|
11257
|
-
const
|
|
11516
|
+
const db2 = c.env.DB;
|
|
11258
11517
|
let query = "SELECT * FROM media";
|
|
11259
11518
|
const params = [];
|
|
11260
11519
|
const conditions = ["deleted_at IS NULL"];
|
|
@@ -11282,9 +11541,9 @@ adminMediaRoutes.get("/", async (c) => {
|
|
|
11282
11541
|
query += ` WHERE ${conditions.join(" AND ")}`;
|
|
11283
11542
|
}
|
|
11284
11543
|
query += ` ORDER BY uploaded_at DESC LIMIT ${limit} OFFSET ${offset}`;
|
|
11285
|
-
const stmt =
|
|
11544
|
+
const stmt = db2.prepare(query);
|
|
11286
11545
|
const { results } = await stmt.bind(...params).all();
|
|
11287
|
-
const foldersStmt =
|
|
11546
|
+
const foldersStmt = db2.prepare(`
|
|
11288
11547
|
SELECT folder, COUNT(*) as count, SUM(size) as totalSize
|
|
11289
11548
|
FROM media
|
|
11290
11549
|
WHERE deleted_at IS NULL
|
|
@@ -11292,7 +11551,7 @@ adminMediaRoutes.get("/", async (c) => {
|
|
|
11292
11551
|
ORDER BY folder
|
|
11293
11552
|
`);
|
|
11294
11553
|
const { results: folders } = await foldersStmt.all();
|
|
11295
|
-
const typesStmt =
|
|
11554
|
+
const typesStmt = db2.prepare(`
|
|
11296
11555
|
SELECT
|
|
11297
11556
|
CASE
|
|
11298
11557
|
WHEN mime_type LIKE 'image/%' THEN 'images'
|
|
@@ -11358,7 +11617,7 @@ adminMediaRoutes.get("/selector", async (c) => {
|
|
|
11358
11617
|
try {
|
|
11359
11618
|
const { searchParams } = new URL(c.req.url);
|
|
11360
11619
|
const search = searchParams.get("search") || "";
|
|
11361
|
-
const
|
|
11620
|
+
const db2 = c.env.DB;
|
|
11362
11621
|
let query = "SELECT * FROM media WHERE deleted_at IS NULL";
|
|
11363
11622
|
const params = [];
|
|
11364
11623
|
if (search.trim()) {
|
|
@@ -11367,7 +11626,7 @@ adminMediaRoutes.get("/selector", async (c) => {
|
|
|
11367
11626
|
params.push(searchTerm, searchTerm, searchTerm);
|
|
11368
11627
|
}
|
|
11369
11628
|
query += " ORDER BY uploaded_at DESC LIMIT 24";
|
|
11370
|
-
const stmt =
|
|
11629
|
+
const stmt = db2.prepare(query);
|
|
11371
11630
|
const { results } = await stmt.bind(...params).all();
|
|
11372
11631
|
const mediaFiles = results.map((row) => ({
|
|
11373
11632
|
id: row.id,
|
|
@@ -11474,7 +11733,7 @@ adminMediaRoutes.get("/search", async (c) => {
|
|
|
11474
11733
|
const search = searchParams.get("search") || "";
|
|
11475
11734
|
const folder = searchParams.get("folder") || "all";
|
|
11476
11735
|
const type = searchParams.get("type") || "all";
|
|
11477
|
-
const
|
|
11736
|
+
const db2 = c.env.DB;
|
|
11478
11737
|
let query = "SELECT * FROM media";
|
|
11479
11738
|
const params = [];
|
|
11480
11739
|
const conditions = [];
|
|
@@ -11507,7 +11766,7 @@ adminMediaRoutes.get("/search", async (c) => {
|
|
|
11507
11766
|
query += ` WHERE ${conditions.join(" AND ")}`;
|
|
11508
11767
|
}
|
|
11509
11768
|
query += ` ORDER BY uploaded_at DESC LIMIT 24`;
|
|
11510
|
-
const stmt =
|
|
11769
|
+
const stmt = db2.prepare(query);
|
|
11511
11770
|
const { results } = await stmt.bind(...params).all();
|
|
11512
11771
|
const mediaFiles = results.map((row) => ({
|
|
11513
11772
|
...row,
|
|
@@ -11530,8 +11789,8 @@ adminMediaRoutes.get("/search", async (c) => {
|
|
|
11530
11789
|
adminMediaRoutes.get("/:id/details", async (c) => {
|
|
11531
11790
|
try {
|
|
11532
11791
|
const id = c.req.param("id");
|
|
11533
|
-
const
|
|
11534
|
-
const stmt =
|
|
11792
|
+
const db2 = c.env.DB;
|
|
11793
|
+
const stmt = db2.prepare("SELECT * FROM media WHERE id = ?");
|
|
11535
11794
|
const result = await stmt.bind(id).first();
|
|
11536
11795
|
if (!result) {
|
|
11537
11796
|
return c.html('<div class="text-red-500">File not found</div>');
|
|
@@ -11604,7 +11863,7 @@ adminMediaRoutes.post("/upload", async (c) => {
|
|
|
11604
11863
|
});
|
|
11605
11864
|
continue;
|
|
11606
11865
|
}
|
|
11607
|
-
const fileId =
|
|
11866
|
+
const fileId = crypto.randomUUID();
|
|
11608
11867
|
const fileExtension = file.name.split(".").pop() || "";
|
|
11609
11868
|
const filename = `${fileId}.${fileExtension}`;
|
|
11610
11869
|
const folder = formData.get("folder") || "uploads";
|
|
@@ -11823,10 +12082,10 @@ adminMediaRoutes.put("/:id", async (c) => {
|
|
|
11823
12082
|
});
|
|
11824
12083
|
adminMediaRoutes.delete("/cleanup", requireRole("admin"), async (c) => {
|
|
11825
12084
|
try {
|
|
11826
|
-
const
|
|
11827
|
-
const allMediaStmt =
|
|
12085
|
+
const db2 = c.env.DB;
|
|
12086
|
+
const allMediaStmt = db2.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
|
|
11828
12087
|
const { results: allMedia } = await allMediaStmt.all();
|
|
11829
|
-
const contentStmt =
|
|
12088
|
+
const contentStmt = db2.prepare("SELECT data FROM content");
|
|
11830
12089
|
const { results: contentRecords } = await contentStmt.all();
|
|
11831
12090
|
const referencedUrls = /* @__PURE__ */ new Set();
|
|
11832
12091
|
for (const record of contentRecords) {
|
|
@@ -11856,7 +12115,7 @@ adminMediaRoutes.delete("/cleanup", requireRole("admin"), async (c) => {
|
|
|
11856
12115
|
for (const file of unusedFiles) {
|
|
11857
12116
|
try {
|
|
11858
12117
|
await c.env.MEDIA_BUCKET.delete(file.r2_key);
|
|
11859
|
-
const deleteStmt =
|
|
12118
|
+
const deleteStmt = db2.prepare("UPDATE media SET deleted_at = ? WHERE id = ?");
|
|
11860
12119
|
await deleteStmt.bind(Math.floor(Date.now() / 1e3), file.id).run();
|
|
11861
12120
|
deletedCount++;
|
|
11862
12121
|
} catch (error) {
|
|
@@ -12080,44 +12339,6 @@ function renderPluginsListPage(data) {
|
|
|
12080
12339
|
<h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Plugins</h1>
|
|
12081
12340
|
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">Manage and extend functionality with plugins</p>
|
|
12082
12341
|
</div>
|
|
12083
|
-
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
|
12084
|
-
<div class="relative inline-block text-left">
|
|
12085
|
-
<button onclick="toggleDropdown()" class="inline-flex items-center justify-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm">
|
|
12086
|
-
<svg class="-ml-0.5 mr-1.5 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
12087
|
-
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
|
|
12088
|
-
</svg>
|
|
12089
|
-
Install Plugin
|
|
12090
|
-
<svg class="-mr-1 ml-2 h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
12091
|
-
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
12092
|
-
</svg>
|
|
12093
|
-
</button>
|
|
12094
|
-
<div id="plugin-dropdown" class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-xl bg-white dark:bg-zinc-900 shadow-xl ring-1 ring-zinc-950/5 dark:ring-white/10 focus:outline-none">
|
|
12095
|
-
<div class="py-1">
|
|
12096
|
-
<button onclick="installPlugin('faq-plugin')" class="block w-full text-left px-4 py-2 text-sm text-zinc-950 dark:text-white hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors first:rounded-t-xl">
|
|
12097
|
-
<div class="flex items-center">
|
|
12098
|
-
<span class="text-lg mr-2">\u2753</span>
|
|
12099
|
-
<div>
|
|
12100
|
-
<div class="font-medium">FAQ System</div>
|
|
12101
|
-
<div class="text-xs text-zinc-500 dark:text-zinc-400">Community FAQ management plugin</div>
|
|
12102
|
-
</div>
|
|
12103
|
-
</div>
|
|
12104
|
-
</button>
|
|
12105
|
-
<div class="border-t border-zinc-950/5 dark:border-white/10 my-1"></div>
|
|
12106
|
-
<button onclick="showNotification('Plugin marketplace coming soon!', 'info')" class="block w-full text-left px-4 py-2 text-sm text-zinc-950 dark:text-white hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors last:rounded-b-xl">
|
|
12107
|
-
<div class="flex items-center">
|
|
12108
|
-
<svg class="w-5 h-5 mr-2 text-zinc-500 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12109
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
|
|
12110
|
-
</svg>
|
|
12111
|
-
<div>
|
|
12112
|
-
<div class="font-medium">Browse Marketplace</div>
|
|
12113
|
-
<div class="text-xs text-zinc-500 dark:text-zinc-400">Discover more plugins</div>
|
|
12114
|
-
</div>
|
|
12115
|
-
</div>
|
|
12116
|
-
</button>
|
|
12117
|
-
</div>
|
|
12118
|
-
</div>
|
|
12119
|
-
</div>
|
|
12120
|
-
</div>
|
|
12121
12342
|
</div>
|
|
12122
12343
|
|
|
12123
12344
|
<!-- Experimental Notice -->
|
|
@@ -12448,16 +12669,11 @@ function renderPluginsListPage(data) {
|
|
|
12448
12669
|
notification.className = \`fixed top-4 right-4 px-4 py-2 rounded-lg text-white z-50 \${bgColor}\`;
|
|
12449
12670
|
notification.textContent = message;
|
|
12450
12671
|
document.body.appendChild(notification);
|
|
12451
|
-
|
|
12672
|
+
|
|
12452
12673
|
setTimeout(() => {
|
|
12453
12674
|
notification.remove();
|
|
12454
12675
|
}, 3000);
|
|
12455
12676
|
}
|
|
12456
|
-
|
|
12457
|
-
function toggleDropdown() {
|
|
12458
|
-
const dropdown = document.getElementById('plugin-dropdown');
|
|
12459
|
-
dropdown.classList.toggle('hidden');
|
|
12460
|
-
}
|
|
12461
12677
|
|
|
12462
12678
|
function filterPlugins() {
|
|
12463
12679
|
const categoryFilter = document.getElementById('category-filter').value.toLowerCase();
|
|
@@ -12524,16 +12740,6 @@ function renderPluginsListPage(data) {
|
|
|
12524
12740
|
noResultsMsg.style.display = 'none';
|
|
12525
12741
|
}
|
|
12526
12742
|
}
|
|
12527
|
-
|
|
12528
|
-
// Close dropdown when clicking outside
|
|
12529
|
-
document.addEventListener('click', (event) => {
|
|
12530
|
-
const dropdown = document.getElementById('plugin-dropdown');
|
|
12531
|
-
const button = event.target.closest('button[onclick="toggleDropdown()"]');
|
|
12532
|
-
|
|
12533
|
-
if (!button && !dropdown.contains(event.target)) {
|
|
12534
|
-
dropdown.classList.add('hidden');
|
|
12535
|
-
}
|
|
12536
|
-
});
|
|
12537
12743
|
</script>
|
|
12538
12744
|
|
|
12539
12745
|
<!-- Confirmation Dialogs -->
|
|
@@ -13557,16 +13763,29 @@ var AVAILABLE_PLUGINS = [
|
|
|
13557
13763
|
permissions: [],
|
|
13558
13764
|
dependencies: [],
|
|
13559
13765
|
is_core: false
|
|
13766
|
+
},
|
|
13767
|
+
{
|
|
13768
|
+
id: "easy-mdx",
|
|
13769
|
+
name: "easy-mdx",
|
|
13770
|
+
display_name: "EasyMDE Markdown Editor",
|
|
13771
|
+
description: "Lightweight markdown editor with live preview. Provides a simple and efficient editor with markdown support for richtext fields.",
|
|
13772
|
+
version: "1.0.0",
|
|
13773
|
+
author: "SonicJS Team",
|
|
13774
|
+
category: "editor",
|
|
13775
|
+
icon: "\u{1F4DD}",
|
|
13776
|
+
permissions: [],
|
|
13777
|
+
dependencies: [],
|
|
13778
|
+
is_core: false
|
|
13560
13779
|
}
|
|
13561
13780
|
];
|
|
13562
13781
|
adminPluginRoutes.get("/", async (c) => {
|
|
13563
13782
|
try {
|
|
13564
13783
|
const user = c.get("user");
|
|
13565
|
-
const
|
|
13784
|
+
const db2 = c.env.DB;
|
|
13566
13785
|
if (user?.role !== "admin") {
|
|
13567
13786
|
return c.text("Access denied", 403);
|
|
13568
13787
|
}
|
|
13569
|
-
const pluginService = new PluginService(
|
|
13788
|
+
const pluginService = new PluginService(db2);
|
|
13570
13789
|
let installedPlugins = [];
|
|
13571
13790
|
let stats = { total: 0, active: 0, inactive: 0, errors: 0, uninstalled: 0 };
|
|
13572
13791
|
try {
|
|
@@ -13633,12 +13852,12 @@ adminPluginRoutes.get("/", async (c) => {
|
|
|
13633
13852
|
adminPluginRoutes.get("/:id", async (c) => {
|
|
13634
13853
|
try {
|
|
13635
13854
|
const user = c.get("user");
|
|
13636
|
-
const
|
|
13855
|
+
const db2 = c.env.DB;
|
|
13637
13856
|
const pluginId = c.req.param("id");
|
|
13638
13857
|
if (user?.role !== "admin") {
|
|
13639
13858
|
return c.redirect("/admin/plugins");
|
|
13640
13859
|
}
|
|
13641
|
-
const pluginService = new PluginService(
|
|
13860
|
+
const pluginService = new PluginService(db2);
|
|
13642
13861
|
const plugin = await pluginService.getPlugin(pluginId);
|
|
13643
13862
|
if (!plugin) {
|
|
13644
13863
|
return c.text("Plugin not found", 404);
|
|
@@ -13687,12 +13906,12 @@ adminPluginRoutes.get("/:id", async (c) => {
|
|
|
13687
13906
|
adminPluginRoutes.post("/:id/activate", async (c) => {
|
|
13688
13907
|
try {
|
|
13689
13908
|
const user = c.get("user");
|
|
13690
|
-
const
|
|
13909
|
+
const db2 = c.env.DB;
|
|
13691
13910
|
const pluginId = c.req.param("id");
|
|
13692
13911
|
if (user?.role !== "admin") {
|
|
13693
13912
|
return c.json({ error: "Access denied" }, 403);
|
|
13694
13913
|
}
|
|
13695
|
-
const pluginService = new PluginService(
|
|
13914
|
+
const pluginService = new PluginService(db2);
|
|
13696
13915
|
await pluginService.activatePlugin(pluginId);
|
|
13697
13916
|
return c.json({ success: true });
|
|
13698
13917
|
} catch (error) {
|
|
@@ -13704,12 +13923,12 @@ adminPluginRoutes.post("/:id/activate", async (c) => {
|
|
|
13704
13923
|
adminPluginRoutes.post("/:id/deactivate", async (c) => {
|
|
13705
13924
|
try {
|
|
13706
13925
|
const user = c.get("user");
|
|
13707
|
-
const
|
|
13926
|
+
const db2 = c.env.DB;
|
|
13708
13927
|
const pluginId = c.req.param("id");
|
|
13709
13928
|
if (user?.role !== "admin") {
|
|
13710
13929
|
return c.json({ error: "Access denied" }, 403);
|
|
13711
13930
|
}
|
|
13712
|
-
const pluginService = new PluginService(
|
|
13931
|
+
const pluginService = new PluginService(db2);
|
|
13713
13932
|
await pluginService.deactivatePlugin(pluginId);
|
|
13714
13933
|
return c.json({ success: true });
|
|
13715
13934
|
} catch (error) {
|
|
@@ -13721,12 +13940,12 @@ adminPluginRoutes.post("/:id/deactivate", async (c) => {
|
|
|
13721
13940
|
adminPluginRoutes.post("/install", async (c) => {
|
|
13722
13941
|
try {
|
|
13723
13942
|
const user = c.get("user");
|
|
13724
|
-
const
|
|
13943
|
+
const db2 = c.env.DB;
|
|
13725
13944
|
if (user?.role !== "admin") {
|
|
13726
13945
|
return c.json({ error: "Access denied" }, 403);
|
|
13727
13946
|
}
|
|
13728
13947
|
const body = await c.req.json();
|
|
13729
|
-
const pluginService = new PluginService(
|
|
13948
|
+
const pluginService = new PluginService(db2);
|
|
13730
13949
|
if (body.name === "faq-plugin") {
|
|
13731
13950
|
const faqPlugin = await pluginService.installPlugin({
|
|
13732
13951
|
id: "third-party-faq",
|
|
@@ -13905,6 +14124,28 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
13905
14124
|
});
|
|
13906
14125
|
return c.json({ success: true, plugin: tinymcePlugin2 });
|
|
13907
14126
|
}
|
|
14127
|
+
if (body.name === "easy-mdx") {
|
|
14128
|
+
const easyMdxPlugin2 = await pluginService.installPlugin({
|
|
14129
|
+
id: "easy-mdx",
|
|
14130
|
+
name: "easy-mdx",
|
|
14131
|
+
display_name: "EasyMDE Markdown Editor",
|
|
14132
|
+
description: "Lightweight markdown editor with live preview. Provides a simple and efficient editor with markdown support for richtext fields.",
|
|
14133
|
+
version: "1.0.0",
|
|
14134
|
+
author: "SonicJS Team",
|
|
14135
|
+
category: "editor",
|
|
14136
|
+
icon: "\u{1F4DD}",
|
|
14137
|
+
permissions: [],
|
|
14138
|
+
dependencies: [],
|
|
14139
|
+
is_core: false,
|
|
14140
|
+
settings: {
|
|
14141
|
+
defaultHeight: 400,
|
|
14142
|
+
theme: "dark",
|
|
14143
|
+
toolbar: "full",
|
|
14144
|
+
placeholder: "Start writing your content..."
|
|
14145
|
+
}
|
|
14146
|
+
});
|
|
14147
|
+
return c.json({ success: true, plugin: easyMdxPlugin2 });
|
|
14148
|
+
}
|
|
13908
14149
|
return c.json({ error: "Plugin not found in registry" }, 404);
|
|
13909
14150
|
} catch (error) {
|
|
13910
14151
|
console.error("Error installing plugin:", error);
|
|
@@ -13915,12 +14156,12 @@ adminPluginRoutes.post("/install", async (c) => {
|
|
|
13915
14156
|
adminPluginRoutes.post("/:id/uninstall", async (c) => {
|
|
13916
14157
|
try {
|
|
13917
14158
|
const user = c.get("user");
|
|
13918
|
-
const
|
|
14159
|
+
const db2 = c.env.DB;
|
|
13919
14160
|
const pluginId = c.req.param("id");
|
|
13920
14161
|
if (user?.role !== "admin") {
|
|
13921
14162
|
return c.json({ error: "Access denied" }, 403);
|
|
13922
14163
|
}
|
|
13923
|
-
const pluginService = new PluginService(
|
|
14164
|
+
const pluginService = new PluginService(db2);
|
|
13924
14165
|
await pluginService.uninstallPlugin(pluginId);
|
|
13925
14166
|
return c.json({ success: true });
|
|
13926
14167
|
} catch (error) {
|
|
@@ -13932,13 +14173,13 @@ adminPluginRoutes.post("/:id/uninstall", async (c) => {
|
|
|
13932
14173
|
adminPluginRoutes.post("/:id/settings", async (c) => {
|
|
13933
14174
|
try {
|
|
13934
14175
|
const user = c.get("user");
|
|
13935
|
-
const
|
|
14176
|
+
const db2 = c.env.DB;
|
|
13936
14177
|
const pluginId = c.req.param("id");
|
|
13937
14178
|
if (user?.role !== "admin") {
|
|
13938
14179
|
return c.json({ error: "Access denied" }, 403);
|
|
13939
14180
|
}
|
|
13940
14181
|
const settings = await c.req.json();
|
|
13941
|
-
const pluginService = new PluginService(
|
|
14182
|
+
const pluginService = new PluginService(db2);
|
|
13942
14183
|
await pluginService.updatePluginSettings(pluginId, settings);
|
|
13943
14184
|
return c.json({ success: true });
|
|
13944
14185
|
} catch (error) {
|
|
@@ -15399,8 +15640,8 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
15399
15640
|
const currentPage = parseInt(page, 10) || 1;
|
|
15400
15641
|
const limit = 20;
|
|
15401
15642
|
const offset = (currentPage - 1) * limit;
|
|
15402
|
-
const
|
|
15403
|
-
if (!
|
|
15643
|
+
const db2 = c.env?.DB;
|
|
15644
|
+
if (!db2) {
|
|
15404
15645
|
return c.html(renderTestimonialsList({
|
|
15405
15646
|
testimonials: [],
|
|
15406
15647
|
totalCount: 0,
|
|
@@ -15431,7 +15672,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
15431
15672
|
params.push(searchTerm, searchTerm, searchTerm);
|
|
15432
15673
|
}
|
|
15433
15674
|
const countQuery = `SELECT COUNT(*) as count FROM testimonials ${whereClause}`;
|
|
15434
|
-
const { results: countResults } = await
|
|
15675
|
+
const { results: countResults } = await db2.prepare(countQuery).bind(...params).all();
|
|
15435
15676
|
const totalCount = countResults?.[0]?.count || 0;
|
|
15436
15677
|
const dataQuery = `
|
|
15437
15678
|
SELECT * FROM testimonials
|
|
@@ -15439,7 +15680,7 @@ adminTestimonialsRoutes.get("/", async (c) => {
|
|
|
15439
15680
|
ORDER BY sortOrder ASC, created_at DESC
|
|
15440
15681
|
LIMIT ? OFFSET ?
|
|
15441
15682
|
`;
|
|
15442
|
-
const { results: testimonials } = await
|
|
15683
|
+
const { results: testimonials } = await db2.prepare(dataQuery).bind(...params, limit, offset).all();
|
|
15443
15684
|
const totalPages = Math.ceil(totalCount / limit);
|
|
15444
15685
|
return c.html(renderTestimonialsList({
|
|
15445
15686
|
testimonials: testimonials || [],
|
|
@@ -15487,8 +15728,8 @@ adminTestimonialsRoutes.post("/", async (c) => {
|
|
|
15487
15728
|
const data = Object.fromEntries(formData.entries());
|
|
15488
15729
|
const validatedData = testimonialSchema.parse(data);
|
|
15489
15730
|
const user = c.get("user");
|
|
15490
|
-
const
|
|
15491
|
-
if (!
|
|
15731
|
+
const db2 = c.env?.DB;
|
|
15732
|
+
if (!db2) {
|
|
15492
15733
|
return c.html(renderTestimonialsForm({
|
|
15493
15734
|
isEdit: false,
|
|
15494
15735
|
user: user ? {
|
|
@@ -15500,7 +15741,7 @@ adminTestimonialsRoutes.post("/", async (c) => {
|
|
|
15500
15741
|
messageType: "error"
|
|
15501
15742
|
}));
|
|
15502
15743
|
}
|
|
15503
|
-
const { results } = await
|
|
15744
|
+
const { results } = await db2.prepare(`
|
|
15504
15745
|
INSERT INTO testimonials (author_name, author_title, author_company, testimonial_text, rating, isPublished, sortOrder)
|
|
15505
15746
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
15506
15747
|
RETURNING *
|
|
@@ -15532,7 +15773,7 @@ adminTestimonialsRoutes.post("/", async (c) => {
|
|
|
15532
15773
|
const user = c.get("user");
|
|
15533
15774
|
if (error instanceof z.ZodError) {
|
|
15534
15775
|
const errors = {};
|
|
15535
|
-
error.
|
|
15776
|
+
error.issues.forEach((err) => {
|
|
15536
15777
|
const field = err.path[0];
|
|
15537
15778
|
if (!errors[field]) errors[field] = [];
|
|
15538
15779
|
errors[field].push(err.message);
|
|
@@ -15565,8 +15806,8 @@ adminTestimonialsRoutes.get("/:id", async (c) => {
|
|
|
15565
15806
|
try {
|
|
15566
15807
|
const id = parseInt(c.req.param("id"));
|
|
15567
15808
|
const user = c.get("user");
|
|
15568
|
-
const
|
|
15569
|
-
if (!
|
|
15809
|
+
const db2 = c.env?.DB;
|
|
15810
|
+
if (!db2) {
|
|
15570
15811
|
return c.html(renderTestimonialsForm({
|
|
15571
15812
|
isEdit: true,
|
|
15572
15813
|
user: user ? {
|
|
@@ -15578,7 +15819,7 @@ adminTestimonialsRoutes.get("/:id", async (c) => {
|
|
|
15578
15819
|
messageType: "error"
|
|
15579
15820
|
}));
|
|
15580
15821
|
}
|
|
15581
|
-
const { results } = await
|
|
15822
|
+
const { results } = await db2.prepare("SELECT * FROM testimonials WHERE id = ?").bind(id).all();
|
|
15582
15823
|
if (!results || results.length === 0) {
|
|
15583
15824
|
return c.redirect("/admin/testimonials?message=Testimonial not found&type=error");
|
|
15584
15825
|
}
|
|
@@ -15623,8 +15864,8 @@ adminTestimonialsRoutes.put("/:id", async (c) => {
|
|
|
15623
15864
|
const data = Object.fromEntries(formData.entries());
|
|
15624
15865
|
const validatedData = testimonialSchema.parse(data);
|
|
15625
15866
|
const user = c.get("user");
|
|
15626
|
-
const
|
|
15627
|
-
if (!
|
|
15867
|
+
const db2 = c.env?.DB;
|
|
15868
|
+
if (!db2) {
|
|
15628
15869
|
return c.html(renderTestimonialsForm({
|
|
15629
15870
|
isEdit: true,
|
|
15630
15871
|
user: user ? {
|
|
@@ -15636,7 +15877,7 @@ adminTestimonialsRoutes.put("/:id", async (c) => {
|
|
|
15636
15877
|
messageType: "error"
|
|
15637
15878
|
}));
|
|
15638
15879
|
}
|
|
15639
|
-
const { results } = await
|
|
15880
|
+
const { results } = await db2.prepare(`
|
|
15640
15881
|
UPDATE testimonials
|
|
15641
15882
|
SET author_name = ?, author_title = ?, author_company = ?, testimonial_text = ?, rating = ?, isPublished = ?, sortOrder = ?
|
|
15642
15883
|
WHERE id = ?
|
|
@@ -15681,7 +15922,7 @@ adminTestimonialsRoutes.put("/:id", async (c) => {
|
|
|
15681
15922
|
const id = parseInt(c.req.param("id"));
|
|
15682
15923
|
if (error instanceof z.ZodError) {
|
|
15683
15924
|
const errors = {};
|
|
15684
|
-
error.
|
|
15925
|
+
error.issues.forEach((err) => {
|
|
15685
15926
|
const field = err.path[0];
|
|
15686
15927
|
if (!errors[field]) errors[field] = [];
|
|
15687
15928
|
errors[field].push(err.message);
|
|
@@ -15733,11 +15974,11 @@ adminTestimonialsRoutes.put("/:id", async (c) => {
|
|
|
15733
15974
|
adminTestimonialsRoutes.delete("/:id", async (c) => {
|
|
15734
15975
|
try {
|
|
15735
15976
|
const id = parseInt(c.req.param("id"));
|
|
15736
|
-
const
|
|
15737
|
-
if (!
|
|
15977
|
+
const db2 = c.env?.DB;
|
|
15978
|
+
if (!db2) {
|
|
15738
15979
|
return c.json({ error: "Database not available" }, 500);
|
|
15739
15980
|
}
|
|
15740
|
-
const { changes } = await
|
|
15981
|
+
const { changes } = await db2.prepare("DELETE FROM testimonials WHERE id = ?").bind(id).run();
|
|
15741
15982
|
if (changes === 0) {
|
|
15742
15983
|
return c.json({ error: "Testimonial not found" }, 404);
|
|
15743
15984
|
}
|
|
@@ -16069,8 +16310,8 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16069
16310
|
const currentPage = parseInt(page, 10) || 1;
|
|
16070
16311
|
const limit = 20;
|
|
16071
16312
|
const offset = (currentPage - 1) * limit;
|
|
16072
|
-
const
|
|
16073
|
-
if (!
|
|
16313
|
+
const db2 = c.env?.DB;
|
|
16314
|
+
if (!db2) {
|
|
16074
16315
|
return c.html(renderCodeExamplesList({
|
|
16075
16316
|
codeExamples: [],
|
|
16076
16317
|
totalCount: 0,
|
|
@@ -16101,7 +16342,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16101
16342
|
params.push(searchTerm, searchTerm, searchTerm, searchTerm);
|
|
16102
16343
|
}
|
|
16103
16344
|
const countQuery = `SELECT COUNT(*) as count FROM code_examples ${whereClause}`;
|
|
16104
|
-
const { results: countResults } = await
|
|
16345
|
+
const { results: countResults } = await db2.prepare(countQuery).bind(...params).all();
|
|
16105
16346
|
const totalCount = countResults?.[0]?.count || 0;
|
|
16106
16347
|
const dataQuery = `
|
|
16107
16348
|
SELECT * FROM code_examples
|
|
@@ -16109,7 +16350,7 @@ adminCodeExamplesRoutes.get("/", async (c) => {
|
|
|
16109
16350
|
ORDER BY sortOrder ASC, created_at DESC
|
|
16110
16351
|
LIMIT ? OFFSET ?
|
|
16111
16352
|
`;
|
|
16112
|
-
const { results: codeExamples } = await
|
|
16353
|
+
const { results: codeExamples } = await db2.prepare(dataQuery).bind(...params, limit, offset).all();
|
|
16113
16354
|
const totalPages = Math.ceil(totalCount / limit);
|
|
16114
16355
|
return c.html(renderCodeExamplesList({
|
|
16115
16356
|
codeExamples: codeExamples || [],
|
|
@@ -16157,8 +16398,8 @@ adminCodeExamplesRoutes.post("/", async (c) => {
|
|
|
16157
16398
|
const data = Object.fromEntries(formData.entries());
|
|
16158
16399
|
const validatedData = codeExampleSchema.parse(data);
|
|
16159
16400
|
const user = c.get("user");
|
|
16160
|
-
const
|
|
16161
|
-
if (!
|
|
16401
|
+
const db2 = c.env?.DB;
|
|
16402
|
+
if (!db2) {
|
|
16162
16403
|
return c.html(renderCodeExamplesForm({
|
|
16163
16404
|
isEdit: false,
|
|
16164
16405
|
user: user ? {
|
|
@@ -16170,7 +16411,7 @@ adminCodeExamplesRoutes.post("/", async (c) => {
|
|
|
16170
16411
|
messageType: "error"
|
|
16171
16412
|
}));
|
|
16172
16413
|
}
|
|
16173
|
-
const { results } = await
|
|
16414
|
+
const { results } = await db2.prepare(`
|
|
16174
16415
|
INSERT INTO code_examples (title, description, code, language, category, tags, isPublished, sortOrder)
|
|
16175
16416
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
16176
16417
|
RETURNING *
|
|
@@ -16203,7 +16444,7 @@ adminCodeExamplesRoutes.post("/", async (c) => {
|
|
|
16203
16444
|
const user = c.get("user");
|
|
16204
16445
|
if (error instanceof z.ZodError) {
|
|
16205
16446
|
const errors = {};
|
|
16206
|
-
error.
|
|
16447
|
+
error.issues.forEach((err) => {
|
|
16207
16448
|
const field = err.path[0];
|
|
16208
16449
|
if (!errors[field]) errors[field] = [];
|
|
16209
16450
|
errors[field].push(err.message);
|
|
@@ -16236,8 +16477,8 @@ adminCodeExamplesRoutes.get("/:id", async (c) => {
|
|
|
16236
16477
|
try {
|
|
16237
16478
|
const id = parseInt(c.req.param("id"));
|
|
16238
16479
|
const user = c.get("user");
|
|
16239
|
-
const
|
|
16240
|
-
if (!
|
|
16480
|
+
const db2 = c.env?.DB;
|
|
16481
|
+
if (!db2) {
|
|
16241
16482
|
return c.html(renderCodeExamplesForm({
|
|
16242
16483
|
isEdit: true,
|
|
16243
16484
|
user: user ? {
|
|
@@ -16249,7 +16490,7 @@ adminCodeExamplesRoutes.get("/:id", async (c) => {
|
|
|
16249
16490
|
messageType: "error"
|
|
16250
16491
|
}));
|
|
16251
16492
|
}
|
|
16252
|
-
const { results } = await
|
|
16493
|
+
const { results } = await db2.prepare("SELECT * FROM code_examples WHERE id = ?").bind(id).all();
|
|
16253
16494
|
if (!results || results.length === 0) {
|
|
16254
16495
|
return c.redirect("/admin/code-examples?message=Code example not found&type=error");
|
|
16255
16496
|
}
|
|
@@ -16295,8 +16536,8 @@ adminCodeExamplesRoutes.put("/:id", async (c) => {
|
|
|
16295
16536
|
const data = Object.fromEntries(formData.entries());
|
|
16296
16537
|
const validatedData = codeExampleSchema.parse(data);
|
|
16297
16538
|
const user = c.get("user");
|
|
16298
|
-
const
|
|
16299
|
-
if (!
|
|
16539
|
+
const db2 = c.env?.DB;
|
|
16540
|
+
if (!db2) {
|
|
16300
16541
|
return c.html(renderCodeExamplesForm({
|
|
16301
16542
|
isEdit: true,
|
|
16302
16543
|
user: user ? {
|
|
@@ -16308,7 +16549,7 @@ adminCodeExamplesRoutes.put("/:id", async (c) => {
|
|
|
16308
16549
|
messageType: "error"
|
|
16309
16550
|
}));
|
|
16310
16551
|
}
|
|
16311
|
-
const { results } = await
|
|
16552
|
+
const { results } = await db2.prepare(`
|
|
16312
16553
|
UPDATE code_examples
|
|
16313
16554
|
SET title = ?, description = ?, code = ?, language = ?, category = ?, tags = ?, isPublished = ?, sortOrder = ?
|
|
16314
16555
|
WHERE id = ?
|
|
@@ -16355,7 +16596,7 @@ adminCodeExamplesRoutes.put("/:id", async (c) => {
|
|
|
16355
16596
|
const id = parseInt(c.req.param("id"));
|
|
16356
16597
|
if (error instanceof z.ZodError) {
|
|
16357
16598
|
const errors = {};
|
|
16358
|
-
error.
|
|
16599
|
+
error.issues.forEach((err) => {
|
|
16359
16600
|
const field = err.path[0];
|
|
16360
16601
|
if (!errors[field]) errors[field] = [];
|
|
16361
16602
|
errors[field].push(err.message);
|
|
@@ -16409,11 +16650,11 @@ adminCodeExamplesRoutes.put("/:id", async (c) => {
|
|
|
16409
16650
|
adminCodeExamplesRoutes.delete("/:id", async (c) => {
|
|
16410
16651
|
try {
|
|
16411
16652
|
const id = parseInt(c.req.param("id"));
|
|
16412
|
-
const
|
|
16413
|
-
if (!
|
|
16653
|
+
const db2 = c.env?.DB;
|
|
16654
|
+
if (!db2) {
|
|
16414
16655
|
return c.json({ error: "Database not available" }, 500);
|
|
16415
16656
|
}
|
|
16416
|
-
const { changes } = await
|
|
16657
|
+
const { changes } = await db2.prepare("DELETE FROM code_examples WHERE id = ?").bind(id).run();
|
|
16417
16658
|
if (changes === 0) {
|
|
16418
16659
|
return c.json({ error: "Code example not found" }, 404);
|
|
16419
16660
|
}
|
|
@@ -17092,10 +17333,10 @@ router.get("/", async (c) => {
|
|
|
17092
17333
|
});
|
|
17093
17334
|
router.get("/stats", async (c) => {
|
|
17094
17335
|
try {
|
|
17095
|
-
const
|
|
17336
|
+
const db2 = c.env.DB;
|
|
17096
17337
|
let collectionsCount = 0;
|
|
17097
17338
|
try {
|
|
17098
|
-
const collectionsStmt =
|
|
17339
|
+
const collectionsStmt = db2.prepare("SELECT COUNT(*) as count FROM collections WHERE is_active = 1");
|
|
17099
17340
|
const collectionsResult = await collectionsStmt.first();
|
|
17100
17341
|
collectionsCount = collectionsResult?.count || 0;
|
|
17101
17342
|
} catch (error) {
|
|
@@ -17103,7 +17344,7 @@ router.get("/stats", async (c) => {
|
|
|
17103
17344
|
}
|
|
17104
17345
|
let contentCount = 0;
|
|
17105
17346
|
try {
|
|
17106
|
-
const contentStmt =
|
|
17347
|
+
const contentStmt = db2.prepare("SELECT COUNT(*) as count FROM content");
|
|
17107
17348
|
const contentResult = await contentStmt.first();
|
|
17108
17349
|
contentCount = contentResult?.count || 0;
|
|
17109
17350
|
} catch (error) {
|
|
@@ -17112,7 +17353,7 @@ router.get("/stats", async (c) => {
|
|
|
17112
17353
|
let mediaCount = 0;
|
|
17113
17354
|
let mediaSize = 0;
|
|
17114
17355
|
try {
|
|
17115
|
-
const mediaStmt =
|
|
17356
|
+
const mediaStmt = db2.prepare("SELECT COUNT(*) as count, COALESCE(SUM(size), 0) as total_size FROM media WHERE deleted_at IS NULL");
|
|
17116
17357
|
const mediaResult = await mediaStmt.first();
|
|
17117
17358
|
mediaCount = mediaResult?.count || 0;
|
|
17118
17359
|
mediaSize = mediaResult?.total_size || 0;
|
|
@@ -17121,7 +17362,7 @@ router.get("/stats", async (c) => {
|
|
|
17121
17362
|
}
|
|
17122
17363
|
let usersCount = 0;
|
|
17123
17364
|
try {
|
|
17124
|
-
const usersStmt =
|
|
17365
|
+
const usersStmt = db2.prepare("SELECT COUNT(*) as count FROM users WHERE is_active = 1");
|
|
17125
17366
|
const usersResult = await usersStmt.first();
|
|
17126
17367
|
usersCount = usersResult?.count || 0;
|
|
17127
17368
|
} catch (error) {
|
|
@@ -17142,17 +17383,17 @@ router.get("/stats", async (c) => {
|
|
|
17142
17383
|
});
|
|
17143
17384
|
router.get("/storage", async (c) => {
|
|
17144
17385
|
try {
|
|
17145
|
-
const
|
|
17386
|
+
const db2 = c.env.DB;
|
|
17146
17387
|
let databaseSize = 0;
|
|
17147
17388
|
try {
|
|
17148
|
-
const result = await
|
|
17389
|
+
const result = await db2.prepare("SELECT 1").run();
|
|
17149
17390
|
databaseSize = result?.meta?.size_after || 0;
|
|
17150
17391
|
} catch (error) {
|
|
17151
17392
|
console.error("Error fetching database size:", error);
|
|
17152
17393
|
}
|
|
17153
17394
|
let mediaSize = 0;
|
|
17154
17395
|
try {
|
|
17155
|
-
const mediaStmt =
|
|
17396
|
+
const mediaStmt = db2.prepare("SELECT COALESCE(SUM(size), 0) as total_size FROM media WHERE deleted_at IS NULL");
|
|
17156
17397
|
const mediaResult = await mediaStmt.first();
|
|
17157
17398
|
mediaSize = mediaResult?.total_size || 0;
|
|
17158
17399
|
} catch (error) {
|
|
@@ -17167,9 +17408,9 @@ router.get("/storage", async (c) => {
|
|
|
17167
17408
|
});
|
|
17168
17409
|
router.get("/recent-activity", async (c) => {
|
|
17169
17410
|
try {
|
|
17170
|
-
const
|
|
17411
|
+
const db2 = c.env.DB;
|
|
17171
17412
|
const limit = parseInt(c.req.query("limit") || "5");
|
|
17172
|
-
const activityStmt =
|
|
17413
|
+
const activityStmt = db2.prepare(`
|
|
17173
17414
|
SELECT
|
|
17174
17415
|
a.id,
|
|
17175
17416
|
a.action,
|
|
@@ -17773,7 +18014,7 @@ function getFieldTypeBadge(fieldType) {
|
|
|
17773
18014
|
"text": "Text",
|
|
17774
18015
|
"richtext": "Rich Text (TinyMCE)",
|
|
17775
18016
|
"quill": "Rich Text (Quill)",
|
|
17776
|
-
"mdxeditor": "
|
|
18017
|
+
"mdxeditor": "EasyMDX",
|
|
17777
18018
|
"number": "Number",
|
|
17778
18019
|
"boolean": "Boolean",
|
|
17779
18020
|
"date": "Date",
|
|
@@ -17796,6 +18037,7 @@ function getFieldTypeBadge(fieldType) {
|
|
|
17796
18037
|
return `<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ${color} ring-1 ring-inset">${label}</span>`;
|
|
17797
18038
|
}
|
|
17798
18039
|
function renderCollectionFormPage(data) {
|
|
18040
|
+
console.log("[renderCollectionFormPage] editorPlugins:", data.editorPlugins);
|
|
17799
18041
|
const isEdit = data.isEdit || !!data.id;
|
|
17800
18042
|
const title = isEdit ? "Edit Collection" : "Create New Collection";
|
|
17801
18043
|
const subtitle = isEdit ? `Update collection: ${data.display_name}` : "Define a new content collection with custom fields and settings.";
|
|
@@ -18260,7 +18502,7 @@ function renderCollectionFormPage(data) {
|
|
|
18260
18502
|
<option value="text">Text</option>
|
|
18261
18503
|
${data.editorPlugins?.tinymce ? '<option value="richtext">Rich Text (TinyMCE)</option>' : ""}
|
|
18262
18504
|
${data.editorPlugins?.quill ? '<option value="quill">Rich Text (Quill)</option>' : ""}
|
|
18263
|
-
${data.editorPlugins?.
|
|
18505
|
+
${data.editorPlugins?.easyMdx ? '<option value="mdxeditor">EasyMDX</option>' : ""}
|
|
18264
18506
|
<option value="number">Number</option>
|
|
18265
18507
|
<option value="boolean">Boolean</option>
|
|
18266
18508
|
<option value="date">Date</option>
|
|
@@ -18482,12 +18724,13 @@ function renderCollectionFormPage(data) {
|
|
|
18482
18724
|
|
|
18483
18725
|
// Check if it's a schema field with field_options that might indicate the actual type
|
|
18484
18726
|
if (field.field_options && typeof field.field_options === 'object') {
|
|
18485
|
-
//
|
|
18486
|
-
if
|
|
18727
|
+
// Only convert to richtext if type is explicitly 'string' and format is richtext
|
|
18728
|
+
// Don't convert if it's already a specific editor type like 'mdxeditor', 'quill', etc.
|
|
18729
|
+
if (field.field_options.format === 'richtext' && uiFieldType === 'string') {
|
|
18487
18730
|
uiFieldType = 'richtext';
|
|
18488
18731
|
}
|
|
18489
18732
|
// Check for other format indicators
|
|
18490
|
-
else if (field.field_options.type) {
|
|
18733
|
+
else if (field.field_options.type && !uiFieldType) {
|
|
18491
18734
|
uiFieldType = field.field_options.type;
|
|
18492
18735
|
}
|
|
18493
18736
|
}
|
|
@@ -18503,8 +18746,60 @@ function renderCollectionFormPage(data) {
|
|
|
18503
18746
|
uiFieldType = typeMapping[uiFieldType];
|
|
18504
18747
|
}
|
|
18505
18748
|
|
|
18749
|
+
// Log all available options
|
|
18750
|
+
const availableOptions = Array.from(fieldTypeSelect.options).map(opt => ({ value: opt.value, text: opt.text }));
|
|
18751
|
+
console.log('Available dropdown options:', availableOptions);
|
|
18752
|
+
console.log('Trying to set field-type to:', uiFieldType);
|
|
18753
|
+
|
|
18754
|
+
// Clear any existing selections first
|
|
18755
|
+
Array.from(fieldTypeSelect.options).forEach(opt => opt.selected = false);
|
|
18756
|
+
|
|
18757
|
+
// Try multiple approaches to set the value
|
|
18758
|
+
let selectionSucceeded = false;
|
|
18759
|
+
|
|
18760
|
+
// Approach 1: Direct value assignment
|
|
18506
18761
|
fieldTypeSelect.value = uiFieldType;
|
|
18507
|
-
|
|
18762
|
+
if (fieldTypeSelect.value === uiFieldType) {
|
|
18763
|
+
selectionSucceeded = true;
|
|
18764
|
+
console.log('\u2713 Approach 1 (direct value) succeeded');
|
|
18765
|
+
}
|
|
18766
|
+
|
|
18767
|
+
// Approach 2: Find and select the specific option
|
|
18768
|
+
if (!selectionSucceeded) {
|
|
18769
|
+
console.log('Approach 1 failed, trying approach 2 (direct option selection)');
|
|
18770
|
+
const optionToSelect = Array.from(fieldTypeSelect.options).find(opt => opt.value === uiFieldType);
|
|
18771
|
+
if (optionToSelect) {
|
|
18772
|
+
optionToSelect.selected = true;
|
|
18773
|
+
// Trigger change event
|
|
18774
|
+
fieldTypeSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
|
18775
|
+
if (fieldTypeSelect.value === uiFieldType) {
|
|
18776
|
+
selectionSucceeded = true;
|
|
18777
|
+
console.log('\u2713 Approach 2 (option.selected) succeeded');
|
|
18778
|
+
}
|
|
18779
|
+
}
|
|
18780
|
+
}
|
|
18781
|
+
|
|
18782
|
+
// Approach 3: Set selectedIndex
|
|
18783
|
+
if (!selectionSucceeded) {
|
|
18784
|
+
console.log('Approach 2 failed, trying approach 3 (selectedIndex)');
|
|
18785
|
+
const optionIndex = Array.from(fieldTypeSelect.options).findIndex(opt => opt.value === uiFieldType);
|
|
18786
|
+
if (optionIndex !== -1) {
|
|
18787
|
+
fieldTypeSelect.selectedIndex = optionIndex;
|
|
18788
|
+
if (fieldTypeSelect.value === uiFieldType) {
|
|
18789
|
+
selectionSucceeded = true;
|
|
18790
|
+
console.log('\u2713 Approach 3 (selectedIndex) succeeded');
|
|
18791
|
+
}
|
|
18792
|
+
}
|
|
18793
|
+
}
|
|
18794
|
+
|
|
18795
|
+
console.log('Final field-type value:', fieldTypeSelect.value, '(wanted:', uiFieldType, ')');
|
|
18796
|
+
|
|
18797
|
+
if (!selectionSucceeded) {
|
|
18798
|
+
console.error('\u274C All approaches failed to set field-type!');
|
|
18799
|
+
console.error('Wanted:', uiFieldType);
|
|
18800
|
+
console.error('Got:', fieldTypeSelect.value);
|
|
18801
|
+
console.error('Available options:', availableOptions);
|
|
18802
|
+
}
|
|
18508
18803
|
} else {
|
|
18509
18804
|
console.error('field-type select not found!');
|
|
18510
18805
|
}
|
|
@@ -18575,9 +18870,15 @@ function renderCollectionFormPage(data) {
|
|
|
18575
18870
|
setTimeout(() => {
|
|
18576
18871
|
isEditingField = false;
|
|
18577
18872
|
console.log('Cleared isEditingField flag');
|
|
18578
|
-
}, 100);
|
|
18579
18873
|
|
|
18580
|
-
|
|
18874
|
+
// Double-check the field-type value after the flag is cleared
|
|
18875
|
+
const finalCheck = document.getElementById('field-type');
|
|
18876
|
+
if (finalCheck) {
|
|
18877
|
+
console.log('Post-flag-clear check - field-type value:', finalCheck.value);
|
|
18878
|
+
}
|
|
18879
|
+
}, 200); // Increased delay
|
|
18880
|
+
|
|
18881
|
+
}, 50); // Increased delay to ensure modal is fully rendered
|
|
18581
18882
|
}
|
|
18582
18883
|
|
|
18583
18884
|
function closeFieldModal() {
|
|
@@ -18762,13 +19063,13 @@ adminCollectionsRoutes.use("*", requireAuth());
|
|
|
18762
19063
|
adminCollectionsRoutes.get("/", async (c) => {
|
|
18763
19064
|
try {
|
|
18764
19065
|
const user = c.get("user");
|
|
18765
|
-
const
|
|
19066
|
+
const db2 = c.env.DB;
|
|
18766
19067
|
const url = new URL(c.req.url);
|
|
18767
19068
|
const search = url.searchParams.get("search") || "";
|
|
18768
19069
|
let stmt;
|
|
18769
19070
|
let results;
|
|
18770
19071
|
if (search) {
|
|
18771
|
-
stmt =
|
|
19072
|
+
stmt = db2.prepare(`
|
|
18772
19073
|
SELECT id, name, display_name, description, created_at, managed, schema
|
|
18773
19074
|
FROM collections
|
|
18774
19075
|
WHERE is_active = 1
|
|
@@ -18779,11 +19080,11 @@ adminCollectionsRoutes.get("/", async (c) => {
|
|
|
18779
19080
|
const queryResults = await stmt.bind(searchParam, searchParam, searchParam).all();
|
|
18780
19081
|
results = queryResults.results;
|
|
18781
19082
|
} else {
|
|
18782
|
-
stmt =
|
|
19083
|
+
stmt = db2.prepare("SELECT id, name, display_name, description, created_at, managed, schema FROM collections WHERE is_active = 1 ORDER BY created_at DESC");
|
|
18783
19084
|
const queryResults = await stmt.all();
|
|
18784
19085
|
results = queryResults.results;
|
|
18785
19086
|
}
|
|
18786
|
-
const fieldCountStmt =
|
|
19087
|
+
const fieldCountStmt = db2.prepare("SELECT collection_id, COUNT(*) as count FROM content_fields GROUP BY collection_id");
|
|
18787
19088
|
const { results: fieldCountResults } = await fieldCountStmt.all();
|
|
18788
19089
|
const fieldCounts = new Map((fieldCountResults || []).map((row) => [String(row.collection_id), Number(row.count)]));
|
|
18789
19090
|
const collections = (results || []).filter((row) => row && row.id).map((row) => {
|
|
@@ -18824,21 +19125,22 @@ adminCollectionsRoutes.get("/", async (c) => {
|
|
|
18824
19125
|
return c.html(renderCollectionsListPage(pageData));
|
|
18825
19126
|
} catch (error) {
|
|
18826
19127
|
console.error("Error fetching collections:", error);
|
|
18827
|
-
|
|
19128
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
19129
|
+
return c.html(html`<p>Error loading collections: ${errorMessage}</p>`);
|
|
18828
19130
|
}
|
|
18829
19131
|
});
|
|
18830
19132
|
adminCollectionsRoutes.get("/new", async (c) => {
|
|
18831
19133
|
const user = c.get("user");
|
|
18832
|
-
const
|
|
19134
|
+
const db2 = c.env.DB;
|
|
18833
19135
|
const [tinymceActive, quillActive, mdxeditorActive] = await Promise.all([
|
|
18834
|
-
isPluginActive2(
|
|
18835
|
-
isPluginActive2(
|
|
18836
|
-
isPluginActive2(
|
|
19136
|
+
isPluginActive2(db2, "tinymce-plugin"),
|
|
19137
|
+
isPluginActive2(db2, "quill-editor"),
|
|
19138
|
+
isPluginActive2(db2, "easy-mdx")
|
|
18837
19139
|
]);
|
|
18838
19140
|
console.log("[Collections /new] Editor plugins status:", {
|
|
18839
19141
|
tinymce: tinymceActive,
|
|
18840
19142
|
quill: quillActive,
|
|
18841
|
-
|
|
19143
|
+
easyMdx: mdxeditorActive
|
|
18842
19144
|
});
|
|
18843
19145
|
const formData = {
|
|
18844
19146
|
isEdit: false,
|
|
@@ -18851,7 +19153,7 @@ adminCollectionsRoutes.get("/new", async (c) => {
|
|
|
18851
19153
|
editorPlugins: {
|
|
18852
19154
|
tinymce: tinymceActive,
|
|
18853
19155
|
quill: quillActive,
|
|
18854
|
-
|
|
19156
|
+
easyMdx: mdxeditorActive
|
|
18855
19157
|
}
|
|
18856
19158
|
};
|
|
18857
19159
|
return c.html(renderCollectionFormPage(formData));
|
|
@@ -18887,8 +19189,8 @@ adminCollectionsRoutes.post("/", async (c) => {
|
|
|
18887
19189
|
return c.redirect("/admin/collections/new");
|
|
18888
19190
|
}
|
|
18889
19191
|
}
|
|
18890
|
-
const
|
|
18891
|
-
const existingStmt =
|
|
19192
|
+
const db2 = c.env.DB;
|
|
19193
|
+
const existingStmt = db2.prepare("SELECT id FROM collections WHERE name = ?");
|
|
18892
19194
|
const existing = await existingStmt.bind(name).first();
|
|
18893
19195
|
if (existing) {
|
|
18894
19196
|
const errorMsg = "A collection with this name already exists.";
|
|
@@ -18924,9 +19226,9 @@ adminCollectionsRoutes.post("/", async (c) => {
|
|
|
18924
19226
|
},
|
|
18925
19227
|
required: ["title"]
|
|
18926
19228
|
};
|
|
18927
|
-
const collectionId =
|
|
19229
|
+
const collectionId = crypto.randomUUID();
|
|
18928
19230
|
const now = Date.now();
|
|
18929
|
-
const insertStmt =
|
|
19231
|
+
const insertStmt = db2.prepare(`
|
|
18930
19232
|
INSERT INTO collections (id, name, display_name, description, schema, is_active, created_at, updated_at)
|
|
18931
19233
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
18932
19234
|
`);
|
|
@@ -18981,10 +19283,15 @@ adminCollectionsRoutes.get("/:id", async (c) => {
|
|
|
18981
19283
|
try {
|
|
18982
19284
|
const id = c.req.param("id");
|
|
18983
19285
|
const user = c.get("user");
|
|
18984
|
-
const
|
|
18985
|
-
const stmt =
|
|
19286
|
+
const db2 = c.env.DB;
|
|
19287
|
+
const stmt = db2.prepare("SELECT * FROM collections WHERE id = ?");
|
|
18986
19288
|
const collection = await stmt.bind(id).first();
|
|
18987
19289
|
if (!collection) {
|
|
19290
|
+
const [tinymceActive2, quillActive2, mdxeditorActive2] = await Promise.all([
|
|
19291
|
+
isPluginActive2(db2, "tinymce-plugin"),
|
|
19292
|
+
isPluginActive2(db2, "quill-editor"),
|
|
19293
|
+
isPluginActive2(db2, "easy-mdx")
|
|
19294
|
+
]);
|
|
18988
19295
|
const formData2 = {
|
|
18989
19296
|
isEdit: true,
|
|
18990
19297
|
error: "Collection not found.",
|
|
@@ -18993,7 +19300,12 @@ adminCollectionsRoutes.get("/:id", async (c) => {
|
|
|
18993
19300
|
email: user.email,
|
|
18994
19301
|
role: user.role
|
|
18995
19302
|
} : void 0,
|
|
18996
|
-
version: c.get("appVersion")
|
|
19303
|
+
version: c.get("appVersion"),
|
|
19304
|
+
editorPlugins: {
|
|
19305
|
+
tinymce: tinymceActive2,
|
|
19306
|
+
quill: quillActive2,
|
|
19307
|
+
easyMdx: mdxeditorActive2
|
|
19308
|
+
}
|
|
18997
19309
|
};
|
|
18998
19310
|
return c.html(renderCollectionFormPage(formData2));
|
|
18999
19311
|
}
|
|
@@ -19019,28 +19331,44 @@ adminCollectionsRoutes.get("/:id", async (c) => {
|
|
|
19019
19331
|
}
|
|
19020
19332
|
}
|
|
19021
19333
|
if (fields.length === 0) {
|
|
19022
|
-
const fieldsStmt =
|
|
19334
|
+
const fieldsStmt = db2.prepare(`
|
|
19023
19335
|
SELECT * FROM content_fields
|
|
19024
19336
|
WHERE collection_id = ?
|
|
19025
19337
|
ORDER BY field_order ASC
|
|
19026
19338
|
`);
|
|
19027
19339
|
const { results: fieldsResults } = await fieldsStmt.bind(id).all();
|
|
19028
|
-
fields = (fieldsResults || []).map((row) =>
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19340
|
+
fields = (fieldsResults || []).map((row) => {
|
|
19341
|
+
let fieldOptions = {};
|
|
19342
|
+
if (row.field_options) {
|
|
19343
|
+
try {
|
|
19344
|
+
fieldOptions = typeof row.field_options === "string" ? JSON.parse(row.field_options) : row.field_options;
|
|
19345
|
+
} catch (e) {
|
|
19346
|
+
console.error("Error parsing field_options for field:", row.field_name, e);
|
|
19347
|
+
fieldOptions = {};
|
|
19348
|
+
}
|
|
19349
|
+
}
|
|
19350
|
+
return {
|
|
19351
|
+
id: row.id,
|
|
19352
|
+
field_name: row.field_name,
|
|
19353
|
+
field_type: row.field_type,
|
|
19354
|
+
field_label: row.field_label,
|
|
19355
|
+
field_options: fieldOptions,
|
|
19356
|
+
field_order: row.field_order,
|
|
19357
|
+
is_required: row.is_required === 1,
|
|
19358
|
+
is_searchable: row.is_searchable === 1
|
|
19359
|
+
};
|
|
19360
|
+
});
|
|
19038
19361
|
}
|
|
19039
19362
|
const [tinymceActive, quillActive, mdxeditorActive] = await Promise.all([
|
|
19040
|
-
isPluginActive2(
|
|
19041
|
-
isPluginActive2(
|
|
19042
|
-
isPluginActive2(
|
|
19363
|
+
isPluginActive2(db2, "tinymce-plugin"),
|
|
19364
|
+
isPluginActive2(db2, "quill-editor"),
|
|
19365
|
+
isPluginActive2(db2, "easy-mdx")
|
|
19043
19366
|
]);
|
|
19367
|
+
console.log("[Collections /:id] Editor plugins status:", {
|
|
19368
|
+
tinymce: tinymceActive,
|
|
19369
|
+
quill: quillActive,
|
|
19370
|
+
easyMdx: mdxeditorActive
|
|
19371
|
+
});
|
|
19044
19372
|
const formData = {
|
|
19045
19373
|
id: collection.id,
|
|
19046
19374
|
name: collection.name,
|
|
@@ -19058,13 +19386,18 @@ adminCollectionsRoutes.get("/:id", async (c) => {
|
|
|
19058
19386
|
editorPlugins: {
|
|
19059
19387
|
tinymce: tinymceActive,
|
|
19060
19388
|
quill: quillActive,
|
|
19061
|
-
|
|
19389
|
+
easyMdx: mdxeditorActive
|
|
19062
19390
|
}
|
|
19063
19391
|
};
|
|
19064
19392
|
return c.html(renderCollectionFormPage(formData));
|
|
19065
19393
|
} catch (error) {
|
|
19066
19394
|
console.error("Error fetching collection:", error);
|
|
19067
19395
|
const user = c.get("user");
|
|
19396
|
+
const [tinymceActive, quillActive, mdxeditorActive] = await Promise.all([
|
|
19397
|
+
isPluginActive2(db, "tinymce-plugin"),
|
|
19398
|
+
isPluginActive2(db, "quill-editor"),
|
|
19399
|
+
isPluginActive2(db, "easy-mdx")
|
|
19400
|
+
]);
|
|
19068
19401
|
const formData = {
|
|
19069
19402
|
isEdit: true,
|
|
19070
19403
|
error: "Failed to load collection.",
|
|
@@ -19073,7 +19406,12 @@ adminCollectionsRoutes.get("/:id", async (c) => {
|
|
|
19073
19406
|
email: user.email,
|
|
19074
19407
|
role: user.role
|
|
19075
19408
|
} : void 0,
|
|
19076
|
-
version: c.get("appVersion")
|
|
19409
|
+
version: c.get("appVersion"),
|
|
19410
|
+
editorPlugins: {
|
|
19411
|
+
tinymce: tinymceActive,
|
|
19412
|
+
quill: quillActive,
|
|
19413
|
+
easyMdx: mdxeditorActive
|
|
19414
|
+
}
|
|
19077
19415
|
};
|
|
19078
19416
|
return c.html(renderCollectionFormPage(formData));
|
|
19079
19417
|
}
|
|
@@ -19091,8 +19429,8 @@ adminCollectionsRoutes.put("/:id", async (c) => {
|
|
|
19091
19429
|
</div>
|
|
19092
19430
|
`);
|
|
19093
19431
|
}
|
|
19094
|
-
const
|
|
19095
|
-
const updateStmt =
|
|
19432
|
+
const db2 = c.env.DB;
|
|
19433
|
+
const updateStmt = db2.prepare(`
|
|
19096
19434
|
UPDATE collections
|
|
19097
19435
|
SET display_name = ?, description = ?, updated_at = ?
|
|
19098
19436
|
WHERE id = ?
|
|
@@ -19115,8 +19453,8 @@ adminCollectionsRoutes.put("/:id", async (c) => {
|
|
|
19115
19453
|
adminCollectionsRoutes.delete("/:id", async (c) => {
|
|
19116
19454
|
try {
|
|
19117
19455
|
const id = c.req.param("id");
|
|
19118
|
-
const
|
|
19119
|
-
const contentStmt =
|
|
19456
|
+
const db2 = c.env.DB;
|
|
19457
|
+
const contentStmt = db2.prepare("SELECT COUNT(*) as count FROM content WHERE collection_id = ?");
|
|
19120
19458
|
const contentResult = await contentStmt.bind(id).first();
|
|
19121
19459
|
if (contentResult && contentResult.count > 0) {
|
|
19122
19460
|
return c.html(html`
|
|
@@ -19125,9 +19463,9 @@ adminCollectionsRoutes.delete("/:id", async (c) => {
|
|
|
19125
19463
|
</div>
|
|
19126
19464
|
`);
|
|
19127
19465
|
}
|
|
19128
|
-
const deleteFieldsStmt =
|
|
19466
|
+
const deleteFieldsStmt = db2.prepare("DELETE FROM content_fields WHERE collection_id = ?");
|
|
19129
19467
|
await deleteFieldsStmt.bind(id).run();
|
|
19130
|
-
const deleteStmt =
|
|
19468
|
+
const deleteStmt = db2.prepare("DELETE FROM collections WHERE id = ?");
|
|
19131
19469
|
await deleteStmt.bind(id).run();
|
|
19132
19470
|
return c.html(html`
|
|
19133
19471
|
<script>
|
|
@@ -19159,18 +19497,18 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
|
|
|
19159
19497
|
if (!/^[a-z0-9_]+$/.test(fieldName)) {
|
|
19160
19498
|
return c.json({ success: false, error: "Field name must contain only lowercase letters, numbers, and underscores." });
|
|
19161
19499
|
}
|
|
19162
|
-
const
|
|
19163
|
-
const existingStmt =
|
|
19500
|
+
const db2 = c.env.DB;
|
|
19501
|
+
const existingStmt = db2.prepare("SELECT id FROM content_fields WHERE collection_id = ? AND field_name = ?");
|
|
19164
19502
|
const existing = await existingStmt.bind(collectionId, fieldName).first();
|
|
19165
19503
|
if (existing) {
|
|
19166
19504
|
return c.json({ success: false, error: "A field with this name already exists." });
|
|
19167
19505
|
}
|
|
19168
|
-
const orderStmt =
|
|
19506
|
+
const orderStmt = db2.prepare("SELECT MAX(field_order) as max_order FROM content_fields WHERE collection_id = ?");
|
|
19169
19507
|
const orderResult = await orderStmt.bind(collectionId).first();
|
|
19170
19508
|
const nextOrder = (orderResult?.max_order || 0) + 1;
|
|
19171
|
-
const fieldId =
|
|
19509
|
+
const fieldId = crypto.randomUUID();
|
|
19172
19510
|
const now = Date.now();
|
|
19173
|
-
const insertStmt =
|
|
19511
|
+
const insertStmt = db2.prepare(`
|
|
19174
19512
|
INSERT INTO content_fields (
|
|
19175
19513
|
id, collection_id, field_name, field_type, field_label,
|
|
19176
19514
|
field_options, field_order, is_required, is_searchable,
|
|
@@ -19219,11 +19557,11 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
|
|
|
19219
19557
|
if (!fieldLabel) {
|
|
19220
19558
|
return c.json({ success: false, error: "Field label is required." });
|
|
19221
19559
|
}
|
|
19222
|
-
const
|
|
19560
|
+
const db2 = c.env.DB;
|
|
19223
19561
|
if (fieldId.startsWith("schema-")) {
|
|
19224
19562
|
const fieldName = fieldId.replace("schema-", "");
|
|
19225
19563
|
console.log("[Field Update] Updating schema field:", fieldName);
|
|
19226
|
-
const getCollectionStmt =
|
|
19564
|
+
const getCollectionStmt = db2.prepare("SELECT * FROM collections WHERE id = ?");
|
|
19227
19565
|
const collection = await getCollectionStmt.bind(collectionId).first();
|
|
19228
19566
|
if (!collection) {
|
|
19229
19567
|
return c.json({ success: false, error: "Collection not found." });
|
|
@@ -19268,7 +19606,7 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
|
|
|
19268
19606
|
console.log("[Field Update] Final required array:", schema.required);
|
|
19269
19607
|
console.log("[Field Update] Final field config:", schema.properties[fieldName]);
|
|
19270
19608
|
}
|
|
19271
|
-
const updateCollectionStmt =
|
|
19609
|
+
const updateCollectionStmt = db2.prepare(`
|
|
19272
19610
|
UPDATE collections
|
|
19273
19611
|
SET schema = ?, updated_at = ?
|
|
19274
19612
|
WHERE id = ?
|
|
@@ -19280,7 +19618,7 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
|
|
|
19280
19618
|
});
|
|
19281
19619
|
return c.json({ success: true });
|
|
19282
19620
|
}
|
|
19283
|
-
const updateStmt =
|
|
19621
|
+
const updateStmt = db2.prepare(`
|
|
19284
19622
|
UPDATE content_fields
|
|
19285
19623
|
SET field_label = ?, field_type = ?, field_options = ?, is_required = ?, is_searchable = ?, updated_at = ?
|
|
19286
19624
|
WHERE id = ?
|
|
@@ -19292,7 +19630,7 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
|
|
|
19292
19630
|
changes: result.meta?.changes,
|
|
19293
19631
|
last_row_id: result.meta?.last_row_id
|
|
19294
19632
|
});
|
|
19295
|
-
const verifyStmt =
|
|
19633
|
+
const verifyStmt = db2.prepare("SELECT * FROM content_fields WHERE id = ?");
|
|
19296
19634
|
const verifyResult = await verifyStmt.bind(fieldId).first();
|
|
19297
19635
|
console.log("[Field Update] Verification - field after update:", verifyResult);
|
|
19298
19636
|
console.log("[Field Update] Successfully updated field with type:", fieldType);
|
|
@@ -19305,8 +19643,8 @@ adminCollectionsRoutes.put("/:collectionId/fields/:fieldId", async (c) => {
|
|
|
19305
19643
|
adminCollectionsRoutes.delete("/:collectionId/fields/:fieldId", async (c) => {
|
|
19306
19644
|
try {
|
|
19307
19645
|
const fieldId = c.req.param("fieldId");
|
|
19308
|
-
const
|
|
19309
|
-
const deleteStmt =
|
|
19646
|
+
const db2 = c.env.DB;
|
|
19647
|
+
const deleteStmt = db2.prepare("DELETE FROM content_fields WHERE id = ?");
|
|
19310
19648
|
await deleteStmt.bind(fieldId).run();
|
|
19311
19649
|
return c.json({ success: true });
|
|
19312
19650
|
} catch (error) {
|
|
@@ -19321,9 +19659,9 @@ adminCollectionsRoutes.post("/:collectionId/fields/reorder", async (c) => {
|
|
|
19321
19659
|
if (!Array.isArray(fieldIds)) {
|
|
19322
19660
|
return c.json({ success: false, error: "Invalid field order data." });
|
|
19323
19661
|
}
|
|
19324
|
-
const
|
|
19662
|
+
const db2 = c.env.DB;
|
|
19325
19663
|
for (let i = 0; i < fieldIds.length; i++) {
|
|
19326
|
-
const updateStmt =
|
|
19664
|
+
const updateStmt = db2.prepare("UPDATE content_fields SET field_order = ?, updated_at = ? WHERE id = ?");
|
|
19327
19665
|
await updateStmt.bind(i + 1, Date.now(), fieldIds[i]).run();
|
|
19328
19666
|
}
|
|
19329
19667
|
return c.json({ success: true });
|
|
@@ -20846,8 +21184,8 @@ adminSettingsRoutes.get("/", (c) => {
|
|
|
20846
21184
|
});
|
|
20847
21185
|
adminSettingsRoutes.get("/general", async (c) => {
|
|
20848
21186
|
const user = c.get("user");
|
|
20849
|
-
const
|
|
20850
|
-
const settingsService = new SettingsService(
|
|
21187
|
+
const db2 = c.env.DB;
|
|
21188
|
+
const settingsService = new SettingsService(db2);
|
|
20851
21189
|
const generalSettings = await settingsService.getGeneralSettings(user?.email);
|
|
20852
21190
|
const mockSettings = getMockSettings(user);
|
|
20853
21191
|
mockSettings.general = generalSettings;
|
|
@@ -20949,8 +21287,8 @@ adminSettingsRoutes.get("/database-tools", (c) => {
|
|
|
20949
21287
|
});
|
|
20950
21288
|
adminSettingsRoutes.get("/api/migrations/status", async (c) => {
|
|
20951
21289
|
try {
|
|
20952
|
-
const
|
|
20953
|
-
const migrationService = new MigrationService(
|
|
21290
|
+
const db2 = c.env.DB;
|
|
21291
|
+
const migrationService = new MigrationService(db2);
|
|
20954
21292
|
const status = await migrationService.getMigrationStatus();
|
|
20955
21293
|
return c.json({
|
|
20956
21294
|
success: true,
|
|
@@ -20973,8 +21311,8 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
20973
21311
|
error: "Unauthorized. Admin access required."
|
|
20974
21312
|
}, 403);
|
|
20975
21313
|
}
|
|
20976
|
-
const
|
|
20977
|
-
const migrationService = new MigrationService(
|
|
21314
|
+
const db2 = c.env.DB;
|
|
21315
|
+
const migrationService = new MigrationService(db2);
|
|
20978
21316
|
const result = await migrationService.runPendingMigrations();
|
|
20979
21317
|
return c.json({
|
|
20980
21318
|
success: result.success,
|
|
@@ -20991,8 +21329,8 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
|
|
|
20991
21329
|
});
|
|
20992
21330
|
adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
|
|
20993
21331
|
try {
|
|
20994
|
-
const
|
|
20995
|
-
const migrationService = new MigrationService(
|
|
21332
|
+
const db2 = c.env.DB;
|
|
21333
|
+
const migrationService = new MigrationService(db2);
|
|
20996
21334
|
const validation = await migrationService.validateSchema();
|
|
20997
21335
|
return c.json({
|
|
20998
21336
|
success: true,
|
|
@@ -21008,8 +21346,8 @@ adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
|
|
|
21008
21346
|
});
|
|
21009
21347
|
adminSettingsRoutes.get("/api/database-tools/stats", async (c) => {
|
|
21010
21348
|
try {
|
|
21011
|
-
const
|
|
21012
|
-
const tablesQuery = await
|
|
21349
|
+
const db2 = c.env.DB;
|
|
21350
|
+
const tablesQuery = await db2.prepare(`
|
|
21013
21351
|
SELECT name FROM sqlite_master
|
|
21014
21352
|
WHERE type='table'
|
|
21015
21353
|
AND name NOT LIKE 'sqlite_%'
|
|
@@ -21021,7 +21359,7 @@ adminSettingsRoutes.get("/api/database-tools/stats", async (c) => {
|
|
|
21021
21359
|
const tableStats = await Promise.all(
|
|
21022
21360
|
tables.map(async (table) => {
|
|
21023
21361
|
try {
|
|
21024
|
-
const countResult = await
|
|
21362
|
+
const countResult = await db2.prepare(`SELECT COUNT(*) as count FROM ${table.name}`).first();
|
|
21025
21363
|
const rowCount = countResult?.count || 0;
|
|
21026
21364
|
totalRows += rowCount;
|
|
21027
21365
|
return {
|
|
@@ -21058,8 +21396,8 @@ adminSettingsRoutes.get("/api/database-tools/stats", async (c) => {
|
|
|
21058
21396
|
});
|
|
21059
21397
|
adminSettingsRoutes.get("/api/database-tools/validate", async (c) => {
|
|
21060
21398
|
try {
|
|
21061
|
-
const
|
|
21062
|
-
const integrityResult = await
|
|
21399
|
+
const db2 = c.env.DB;
|
|
21400
|
+
const integrityResult = await db2.prepare("PRAGMA integrity_check").first();
|
|
21063
21401
|
const isValid = integrityResult?.integrity_check === "ok";
|
|
21064
21402
|
return c.json({
|
|
21065
21403
|
success: true,
|
|
@@ -21114,11 +21452,11 @@ adminSettingsRoutes.post("/api/database-tools/truncate", async (c) => {
|
|
|
21114
21452
|
error: "No tables specified for truncation"
|
|
21115
21453
|
}, 400);
|
|
21116
21454
|
}
|
|
21117
|
-
const
|
|
21455
|
+
const db2 = c.env.DB;
|
|
21118
21456
|
const results = [];
|
|
21119
21457
|
for (const tableName of tablesToTruncate) {
|
|
21120
21458
|
try {
|
|
21121
|
-
await
|
|
21459
|
+
await db2.prepare(`DELETE FROM ${tableName}`).run();
|
|
21122
21460
|
results.push({ table: tableName, success: true });
|
|
21123
21461
|
} catch (error) {
|
|
21124
21462
|
console.error(`Error truncating ${tableName}:`, error);
|
|
@@ -21148,8 +21486,8 @@ adminSettingsRoutes.post("/general", async (c) => {
|
|
|
21148
21486
|
}, 403);
|
|
21149
21487
|
}
|
|
21150
21488
|
const formData = await c.req.formData();
|
|
21151
|
-
const
|
|
21152
|
-
const settingsService = new SettingsService(
|
|
21489
|
+
const db2 = c.env.DB;
|
|
21490
|
+
const settingsService = new SettingsService(db2);
|
|
21153
21491
|
const settings = {
|
|
21154
21492
|
siteName: formData.get("siteName"),
|
|
21155
21493
|
siteDescription: formData.get("siteDescription"),
|
|
@@ -21198,6 +21536,7 @@ var ROUTES_INFO = {
|
|
|
21198
21536
|
"apiSystemRoutes",
|
|
21199
21537
|
"adminApiRoutes",
|
|
21200
21538
|
"authRoutes",
|
|
21539
|
+
"testCleanupRoutes",
|
|
21201
21540
|
"adminContentRoutes",
|
|
21202
21541
|
"adminUsersRoutes",
|
|
21203
21542
|
"adminMediaRoutes",
|
|
@@ -21215,6 +21554,6 @@ var ROUTES_INFO = {
|
|
|
21215
21554
|
reference: "https://github.com/sonicjs/sonicjs"
|
|
21216
21555
|
};
|
|
21217
21556
|
|
|
21218
|
-
export { PluginBuilder, ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, userRoutes };
|
|
21219
|
-
//# sourceMappingURL=chunk-
|
|
21220
|
-
//# sourceMappingURL=chunk-
|
|
21557
|
+
export { PluginBuilder, ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, test_cleanup_default, userRoutes };
|
|
21558
|
+
//# sourceMappingURL=chunk-QWIXOMHW.js.map
|
|
21559
|
+
//# sourceMappingURL=chunk-QWIXOMHW.js.map
|