@sonicjs-cms/core 2.0.4 → 2.0.7
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 +16 -0
- package/dist/{chunk-4BJGEGX5.cjs → chunk-3R7EQNGO.cjs} +19 -32
- package/dist/chunk-3R7EQNGO.cjs.map +1 -0
- package/dist/{chunk-CDBVZEWR.js → chunk-4MBTSUI6.js} +35 -3
- package/dist/chunk-4MBTSUI6.js.map +1 -0
- package/dist/{chunk-LH4Z7QID.js → chunk-6FR25MPC.js} +111 -3
- package/dist/chunk-6FR25MPC.js.map +1 -0
- package/dist/{chunk-3SPQ3J4N.cjs → chunk-7XEESVSX.cjs} +73 -2
- package/dist/chunk-7XEESVSX.cjs.map +1 -0
- package/dist/{chunk-3NVJ6W27.cjs → chunk-DOR2IU73.cjs} +111 -2
- package/dist/chunk-DOR2IU73.cjs.map +1 -0
- package/dist/{chunk-CQ2VMJQO.js → chunk-JB2NUJJ5.js} +253 -85
- package/dist/chunk-JB2NUJJ5.js.map +1 -0
- package/dist/{chunk-5APKEYFK.cjs → chunk-KHNSPJ6X.cjs} +5 -5
- package/dist/{chunk-5APKEYFK.cjs.map → chunk-KHNSPJ6X.cjs.map} +1 -1
- package/dist/{chunk-RYQCT2IV.js → chunk-LS5CMDNL.js} +3 -3
- package/dist/{chunk-RYQCT2IV.js.map → chunk-LS5CMDNL.js.map} +1 -1
- package/dist/{chunk-M6FPVS7E.js → chunk-O7LMFJMZ.js} +16 -29
- package/dist/chunk-O7LMFJMZ.js.map +1 -0
- package/dist/{chunk-RZW752PE.cjs → chunk-OOV64BK4.cjs} +429 -261
- package/dist/chunk-OOV64BK4.cjs.map +1 -0
- package/dist/{chunk-BRPONFW6.cjs → chunk-RSFXIU6A.cjs} +14 -14
- package/dist/{chunk-BRPONFW6.cjs.map → chunk-RSFXIU6A.cjs.map} +1 -1
- package/dist/{chunk-LEG4KNFP.cjs → chunk-YGVWY6KO.cjs} +35 -3
- package/dist/chunk-YGVWY6KO.cjs.map +1 -0
- package/dist/{chunk-3LZ6TLPC.js → chunk-YHLLVUJC.js} +73 -2
- package/dist/chunk-YHLLVUJC.js.map +1 -0
- package/dist/{chunk-WKGONLHK.js → chunk-YURRY22X.js} +14 -14
- package/dist/{chunk-WKGONLHK.js.map → chunk-YURRY22X.js.map} +1 -1
- package/dist/index.cjs +886 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +761 -11
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +23 -23
- package/dist/middleware.js +2 -2
- package/dist/routes.cjs +26 -26
- package/dist/routes.js +6 -6
- package/dist/services.cjs +25 -21
- package/dist/services.js +2 -2
- package/dist/templates.cjs +18 -18
- package/dist/templates.js +2 -2
- package/dist/utils.cjs +11 -11
- package/dist/utils.js +1 -1
- package/migrations/006_plugin_system.sql +2 -2
- package/migrations/011_config_managed_collections.sql +1 -0
- package/migrations/018_settings_table.sql +23 -0
- package/package.json +12 -12
- package/dist/chunk-3LZ6TLPC.js.map +0 -1
- package/dist/chunk-3NVJ6W27.cjs.map +0 -1
- package/dist/chunk-3SPQ3J4N.cjs.map +0 -1
- package/dist/chunk-4BJGEGX5.cjs.map +0 -1
- package/dist/chunk-CDBVZEWR.js.map +0 -1
- package/dist/chunk-CQ2VMJQO.js.map +0 -1
- package/dist/chunk-LEG4KNFP.cjs.map +0 -1
- package/dist/chunk-LH4Z7QID.js.map +0 -1
- package/dist/chunk-M6FPVS7E.js.map +0 -1
- package/dist/chunk-RZW752PE.cjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { getCacheService, CACHE_CONFIGS, getLogger } from './chunk-
|
|
2
|
-
import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-
|
|
3
|
-
import { PluginService, MigrationService } from './chunk-
|
|
4
|
-
import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-
|
|
5
|
-
import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-
|
|
1
|
+
import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-6FR25MPC.js';
|
|
2
|
+
import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-O7LMFJMZ.js';
|
|
3
|
+
import { PluginService, MigrationService } from './chunk-4MBTSUI6.js';
|
|
4
|
+
import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-YHLLVUJC.js';
|
|
5
|
+
import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-YURRY22X.js';
|
|
6
6
|
import { metricsTracker } from './chunk-FICTAGD4.js';
|
|
7
7
|
import { Hono } from 'hono';
|
|
8
8
|
import { cors } from 'hono/cors';
|
|
@@ -78,7 +78,7 @@ apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
|
|
|
78
78
|
title,
|
|
79
79
|
JSON.stringify(data || {}),
|
|
80
80
|
status || "draft",
|
|
81
|
-
user?.userId || "
|
|
81
|
+
user?.userId || "system",
|
|
82
82
|
now,
|
|
83
83
|
now
|
|
84
84
|
).run();
|
|
@@ -671,6 +671,7 @@ apiMediaRoutes.post("/upload", async (c) => {
|
|
|
671
671
|
size: mediaRecord.size,
|
|
672
672
|
width: mediaRecord.width,
|
|
673
673
|
height: mediaRecord.height,
|
|
674
|
+
r2_key: mediaRecord.r2_key,
|
|
674
675
|
publicUrl: mediaRecord.public_url,
|
|
675
676
|
thumbnailUrl: mediaRecord.thumbnail_url,
|
|
676
677
|
uploadedAt: new Date(mediaRecord.uploaded_at * 1e3).toISOString()
|
|
@@ -797,6 +798,7 @@ apiMediaRoutes.post("/upload-multiple", async (c) => {
|
|
|
797
798
|
size: mediaRecord.size,
|
|
798
799
|
width: mediaRecord.width,
|
|
799
800
|
height: mediaRecord.height,
|
|
801
|
+
r2_key: mediaRecord.r2_key,
|
|
800
802
|
publicUrl: mediaRecord.public_url,
|
|
801
803
|
thumbnailUrl: mediaRecord.thumbnail_url,
|
|
802
804
|
uploadedAt: new Date(mediaRecord.uploaded_at * 1e3).toISOString()
|
|
@@ -1440,8 +1442,12 @@ adminApiRoutes.get("/activity", async (c) => {
|
|
|
1440
1442
|
});
|
|
1441
1443
|
var createCollectionSchema = z.object({
|
|
1442
1444
|
name: z.string().min(1).max(255).regex(/^[a-z0-9_]+$/, "Must contain only lowercase letters, numbers, and underscores"),
|
|
1443
|
-
|
|
1445
|
+
displayName: z.string().min(1).max(255).optional(),
|
|
1446
|
+
display_name: z.string().min(1).max(255).optional(),
|
|
1444
1447
|
description: z.string().optional()
|
|
1448
|
+
}).refine((data) => data.displayName || data.display_name, {
|
|
1449
|
+
message: "Either displayName or display_name is required",
|
|
1450
|
+
path: ["displayName"]
|
|
1445
1451
|
});
|
|
1446
1452
|
var updateCollectionSchema = z.object({
|
|
1447
1453
|
display_name: z.string().min(1).max(255).optional(),
|
|
@@ -1528,15 +1534,16 @@ adminApiRoutes.get("/collections/:id", async (c) => {
|
|
|
1528
1534
|
updated_at: Number(row.updated_at)
|
|
1529
1535
|
}));
|
|
1530
1536
|
return c.json({
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1537
|
+
id: collection.id,
|
|
1538
|
+
name: collection.name,
|
|
1539
|
+
display_name: collection.display_name,
|
|
1540
|
+
description: collection.description,
|
|
1541
|
+
is_active: collection.is_active === 1,
|
|
1542
|
+
managed: collection.managed === 1,
|
|
1543
|
+
schema: collection.schema ? JSON.parse(collection.schema) : null,
|
|
1544
|
+
created_at: Number(collection.created_at),
|
|
1545
|
+
updated_at: Number(collection.updated_at),
|
|
1546
|
+
fields
|
|
1540
1547
|
});
|
|
1541
1548
|
} catch (error) {
|
|
1542
1549
|
console.error("Error fetching collection:", error);
|
|
@@ -1545,7 +1552,16 @@ adminApiRoutes.get("/collections/:id", async (c) => {
|
|
|
1545
1552
|
});
|
|
1546
1553
|
adminApiRoutes.post("/collections", async (c) => {
|
|
1547
1554
|
try {
|
|
1548
|
-
const
|
|
1555
|
+
const contentType = c.req.header("Content-Type");
|
|
1556
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
1557
|
+
return c.json({ error: "Content-Type must be application/json" }, 400);
|
|
1558
|
+
}
|
|
1559
|
+
let body;
|
|
1560
|
+
try {
|
|
1561
|
+
body = await c.req.json();
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
return c.json({ error: "Invalid JSON in request body" }, 400);
|
|
1564
|
+
}
|
|
1549
1565
|
const validation = createCollectionSchema.safeParse(body);
|
|
1550
1566
|
if (!validation.success) {
|
|
1551
1567
|
return c.json({ error: "Validation failed", details: validation.error.errors }, 400);
|
|
@@ -1553,6 +1569,7 @@ adminApiRoutes.post("/collections", async (c) => {
|
|
|
1553
1569
|
const validatedData = validation.data;
|
|
1554
1570
|
const db = c.env.DB;
|
|
1555
1571
|
const user = c.get("user");
|
|
1572
|
+
const displayName = validatedData.displayName || validatedData.display_name || "";
|
|
1556
1573
|
const existingStmt = db.prepare("SELECT id FROM collections WHERE name = ?");
|
|
1557
1574
|
const existing = await existingStmt.bind(validatedData.name).first();
|
|
1558
1575
|
if (existing) {
|
|
@@ -1589,7 +1606,7 @@ adminApiRoutes.post("/collections", async (c) => {
|
|
|
1589
1606
|
await insertStmt.bind(
|
|
1590
1607
|
collectionId,
|
|
1591
1608
|
validatedData.name,
|
|
1592
|
-
|
|
1609
|
+
displayName,
|
|
1593
1610
|
validatedData.description || null,
|
|
1594
1611
|
JSON.stringify(basicSchema),
|
|
1595
1612
|
1,
|
|
@@ -1604,13 +1621,11 @@ adminApiRoutes.post("/collections", async (c) => {
|
|
|
1604
1621
|
console.error("Error clearing cache:", e);
|
|
1605
1622
|
}
|
|
1606
1623
|
return c.json({
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
created_at: now
|
|
1613
|
-
}
|
|
1624
|
+
id: collectionId,
|
|
1625
|
+
name: validatedData.name,
|
|
1626
|
+
displayName,
|
|
1627
|
+
description: validatedData.description,
|
|
1628
|
+
created_at: now
|
|
1614
1629
|
}, 201);
|
|
1615
1630
|
} catch (error) {
|
|
1616
1631
|
console.error("Error creating collection:", error);
|
|
@@ -1674,6 +1689,11 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
1674
1689
|
try {
|
|
1675
1690
|
const id = c.req.param("id");
|
|
1676
1691
|
const db = c.env.DB;
|
|
1692
|
+
const collectionStmt = db.prepare("SELECT name FROM collections WHERE id = ?");
|
|
1693
|
+
const collection = await collectionStmt.bind(id).first();
|
|
1694
|
+
if (!collection) {
|
|
1695
|
+
return c.json({ error: "Collection not found" }, 404);
|
|
1696
|
+
}
|
|
1677
1697
|
const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content WHERE collection_id = ?");
|
|
1678
1698
|
const contentResult = await contentStmt.bind(id).first();
|
|
1679
1699
|
if (contentResult && contentResult.count > 0) {
|
|
@@ -1681,17 +1701,13 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
|
|
|
1681
1701
|
error: `Cannot delete collection: it contains ${contentResult.count} content item(s). Delete all content first.`
|
|
1682
1702
|
}, 400);
|
|
1683
1703
|
}
|
|
1684
|
-
const collectionStmt = db.prepare("SELECT name FROM collections WHERE id = ?");
|
|
1685
|
-
const collection = await collectionStmt.bind(id).first();
|
|
1686
1704
|
const deleteFieldsStmt = db.prepare("DELETE FROM content_fields WHERE collection_id = ?");
|
|
1687
1705
|
await deleteFieldsStmt.bind(id).run();
|
|
1688
1706
|
const deleteStmt = db.prepare("DELETE FROM collections WHERE id = ?");
|
|
1689
1707
|
await deleteStmt.bind(id).run();
|
|
1690
1708
|
try {
|
|
1691
1709
|
await c.env.CACHE_KV.delete("cache:collections:all");
|
|
1692
|
-
|
|
1693
|
-
await c.env.CACHE_KV.delete(`cache:collection:${collection.name}`);
|
|
1694
|
-
}
|
|
1710
|
+
await c.env.CACHE_KV.delete(`cache:collection:${collection.name}`);
|
|
1695
1711
|
} catch (e) {
|
|
1696
1712
|
console.error("Error clearing cache:", e);
|
|
1697
1713
|
}
|
|
@@ -5190,11 +5206,11 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
5190
5206
|
const now = Date.now();
|
|
5191
5207
|
const insertStmt = db.prepare(`
|
|
5192
5208
|
INSERT INTO content (
|
|
5193
|
-
id, collection_id, slug, title, data, status,
|
|
5209
|
+
id, collection_id, slug, title, data, status,
|
|
5194
5210
|
scheduled_publish_at, scheduled_unpublish_at,
|
|
5195
|
-
meta_title, meta_description, author_id, created_at, updated_at
|
|
5211
|
+
meta_title, meta_description, author_id, created_by, created_at, updated_at
|
|
5196
5212
|
)
|
|
5197
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5213
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5198
5214
|
`);
|
|
5199
5215
|
await insertStmt.bind(
|
|
5200
5216
|
contentId,
|
|
@@ -5208,6 +5224,7 @@ adminContentRoutes.post("/", async (c) => {
|
|
|
5208
5224
|
data.meta_title || null,
|
|
5209
5225
|
data.meta_description || null,
|
|
5210
5226
|
user?.userId || "unknown",
|
|
5227
|
+
user?.userId || "unknown",
|
|
5211
5228
|
now,
|
|
5212
5229
|
now
|
|
5213
5230
|
).run();
|
|
@@ -5509,10 +5526,10 @@ adminContentRoutes.post("/duplicate", async (c) => {
|
|
|
5509
5526
|
originalData.title = `${originalData.title || "Untitled"} (Copy)`;
|
|
5510
5527
|
const insertStmt = db.prepare(`
|
|
5511
5528
|
INSERT INTO content (
|
|
5512
|
-
id, collection_id, slug, title, data, status,
|
|
5513
|
-
author_id, created_at, updated_at
|
|
5529
|
+
id, collection_id, slug, title, data, status,
|
|
5530
|
+
author_id, created_by, created_at, updated_at
|
|
5514
5531
|
)
|
|
5515
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5532
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
5516
5533
|
`);
|
|
5517
5534
|
await insertStmt.bind(
|
|
5518
5535
|
newId,
|
|
@@ -5523,6 +5540,7 @@ adminContentRoutes.post("/duplicate", async (c) => {
|
|
|
5523
5540
|
"draft",
|
|
5524
5541
|
// Always start as draft
|
|
5525
5542
|
user?.userId || "unknown",
|
|
5543
|
+
user?.userId || "unknown",
|
|
5526
5544
|
now,
|
|
5527
5545
|
now
|
|
5528
5546
|
).run();
|
|
@@ -5873,6 +5891,11 @@ var admin_content_default = adminContentRoutes;
|
|
|
5873
5891
|
|
|
5874
5892
|
// src/templates/pages/admin-profile.template.ts
|
|
5875
5893
|
init_admin_layout_catalyst_template();
|
|
5894
|
+
function renderAvatarImage(avatarUrl, firstName, lastName) {
|
|
5895
|
+
return `<div id="avatar-image-container" class="w-24 h-24 rounded-full mx-auto mb-4 overflow-hidden bg-gradient-to-br from-cyan-400 to-purple-400 flex items-center justify-center ring-4 ring-zinc-950/5 dark:ring-white/10">
|
|
5896
|
+
${avatarUrl ? `<img src="${avatarUrl}" alt="Profile picture" class="w-full h-full object-cover">` : `<span class="text-2xl font-bold text-white">${firstName.charAt(0)}${lastName.charAt(0)}</span>`}
|
|
5897
|
+
</div>`;
|
|
5898
|
+
}
|
|
5876
5899
|
function renderProfilePage(data) {
|
|
5877
5900
|
const pageContent = `
|
|
5878
5901
|
<div class="space-y-8">
|
|
@@ -6071,9 +6094,7 @@ function renderProfilePage(data) {
|
|
|
6071
6094
|
<h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Profile Picture</h3>
|
|
6072
6095
|
|
|
6073
6096
|
<div class="text-center">
|
|
6074
|
-
|
|
6075
|
-
${data.profile.avatar_url ? `<img src="${data.profile.avatar_url}" alt="Profile picture" class="w-full h-full object-cover">` : `<span class="text-2xl font-bold text-white">${data.profile.first_name.charAt(0)}${data.profile.last_name.charAt(0)}</span>`}
|
|
6076
|
-
</div>
|
|
6097
|
+
${renderAvatarImage(data.profile.avatar_url, data.profile.first_name, data.profile.last_name)}
|
|
6077
6098
|
|
|
6078
6099
|
<form id="avatar-form" hx-post="/admin/profile/avatar" hx-target="#avatar-messages" hx-encoding="multipart/form-data">
|
|
6079
6100
|
<input
|
|
@@ -7909,6 +7930,10 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
7909
7930
|
WHERE id = ?
|
|
7910
7931
|
`);
|
|
7911
7932
|
await updateStmt.bind(avatarUrl, Date.now(), user.userId).run();
|
|
7933
|
+
const userStmt = db.prepare(`
|
|
7934
|
+
SELECT first_name, last_name FROM users WHERE id = ?
|
|
7935
|
+
`);
|
|
7936
|
+
const userData = await userStmt.bind(user.userId).first();
|
|
7912
7937
|
await logActivity(
|
|
7913
7938
|
db,
|
|
7914
7939
|
user.userId,
|
|
@@ -7919,11 +7944,18 @@ userRoutes.post("/profile/avatar", async (c) => {
|
|
|
7919
7944
|
c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
|
|
7920
7945
|
c.req.header("user-agent")
|
|
7921
7946
|
);
|
|
7922
|
-
|
|
7947
|
+
const alertHtml = renderAlert2({
|
|
7923
7948
|
type: "success",
|
|
7924
7949
|
message: "Profile picture updated successfully!",
|
|
7925
7950
|
dismissible: true
|
|
7926
|
-
})
|
|
7951
|
+
});
|
|
7952
|
+
const avatarUrlWithCache = `${avatarUrl}?t=${Date.now()}`;
|
|
7953
|
+
const avatarImageHtml = renderAvatarImage(avatarUrlWithCache, userData.first_name, userData.last_name);
|
|
7954
|
+
const avatarImageWithOob = avatarImageHtml.replace(
|
|
7955
|
+
'id="avatar-image-container"',
|
|
7956
|
+
'id="avatar-image-container" hx-swap-oob="true"'
|
|
7957
|
+
);
|
|
7958
|
+
return c.html(alertHtml + avatarImageWithOob);
|
|
7927
7959
|
} catch (error) {
|
|
7928
7960
|
console.error("Avatar upload error:", error);
|
|
7929
7961
|
return c.html(renderAlert2({
|
|
@@ -9155,8 +9187,10 @@ function renderMediaLibraryPage(data) {
|
|
|
9155
9187
|
</button>
|
|
9156
9188
|
<button
|
|
9157
9189
|
class="w-full text-left px-3 py-2 text-sm text-zinc-700 dark:text-zinc-300 hover:text-zinc-950 dark:hover:text-white hover:bg-zinc-50 dark:hover:bg-zinc-800/50 rounded-lg transition-colors"
|
|
9158
|
-
hx-delete="/media/cleanup"
|
|
9190
|
+
hx-delete="/admin/media/cleanup"
|
|
9159
9191
|
hx-confirm="Delete unused files?"
|
|
9192
|
+
hx-target="body"
|
|
9193
|
+
hx-swap="beforeend"
|
|
9160
9194
|
>
|
|
9161
9195
|
Cleanup Unused
|
|
9162
9196
|
</button>
|
|
@@ -10759,6 +10793,87 @@ adminMediaRoutes.put("/:id", async (c) => {
|
|
|
10759
10793
|
`);
|
|
10760
10794
|
}
|
|
10761
10795
|
});
|
|
10796
|
+
adminMediaRoutes.delete("/cleanup", requireRole("admin"), async (c) => {
|
|
10797
|
+
try {
|
|
10798
|
+
const db = c.env.DB;
|
|
10799
|
+
const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
|
|
10800
|
+
const { results: allMedia } = await allMediaStmt.all();
|
|
10801
|
+
const contentStmt = db.prepare("SELECT data FROM content");
|
|
10802
|
+
const { results: contentRecords } = await contentStmt.all();
|
|
10803
|
+
const referencedUrls = /* @__PURE__ */ new Set();
|
|
10804
|
+
for (const record of contentRecords) {
|
|
10805
|
+
if (record.data) {
|
|
10806
|
+
const dataStr = typeof record.data === "string" ? record.data : JSON.stringify(record.data);
|
|
10807
|
+
const urlMatches = dataStr.matchAll(/\/files\/([^\s"',]+)/g);
|
|
10808
|
+
for (const match of urlMatches) {
|
|
10809
|
+
referencedUrls.add(match[1]);
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
const unusedFiles = allMedia.filter((file) => !referencedUrls.has(file.r2_key));
|
|
10814
|
+
if (unusedFiles.length === 0) {
|
|
10815
|
+
return c.html(html`
|
|
10816
|
+
<div class="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded">
|
|
10817
|
+
No unused media files found. All files are referenced in content.
|
|
10818
|
+
</div>
|
|
10819
|
+
<script>
|
|
10820
|
+
setTimeout(() => {
|
|
10821
|
+
window.location.href = '/admin/media?t=' + Date.now();
|
|
10822
|
+
}, 2000);
|
|
10823
|
+
</script>
|
|
10824
|
+
`);
|
|
10825
|
+
}
|
|
10826
|
+
let deletedCount = 0;
|
|
10827
|
+
const errors = [];
|
|
10828
|
+
for (const file of unusedFiles) {
|
|
10829
|
+
try {
|
|
10830
|
+
await c.env.MEDIA_BUCKET.delete(file.r2_key);
|
|
10831
|
+
const deleteStmt = db.prepare("UPDATE media SET deleted_at = ? WHERE id = ?");
|
|
10832
|
+
await deleteStmt.bind(Math.floor(Date.now() / 1e3), file.id).run();
|
|
10833
|
+
deletedCount++;
|
|
10834
|
+
} catch (error) {
|
|
10835
|
+
console.error(`Failed to delete ${file.filename}:`, error);
|
|
10836
|
+
errors.push({
|
|
10837
|
+
filename: file.filename,
|
|
10838
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
10839
|
+
});
|
|
10840
|
+
}
|
|
10841
|
+
}
|
|
10842
|
+
return c.html(html`
|
|
10843
|
+
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
|
10844
|
+
Successfully cleaned up ${deletedCount} unused media file${deletedCount !== 1 ? "s" : ""}.
|
|
10845
|
+
${errors.length > 0 ? html`
|
|
10846
|
+
<br><span class="text-sm">Failed to delete ${errors.length} file${errors.length !== 1 ? "s" : ""}.</span>
|
|
10847
|
+
` : ""}
|
|
10848
|
+
</div>
|
|
10849
|
+
|
|
10850
|
+
${errors.length > 0 ? html`
|
|
10851
|
+
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
10852
|
+
<p class="font-medium">Cleanup errors:</p>
|
|
10853
|
+
<ul class="list-disc list-inside mt-2 text-sm">
|
|
10854
|
+
${errors.map((error) => html`
|
|
10855
|
+
<li>${error.filename}: ${error.error}</li>
|
|
10856
|
+
`)}
|
|
10857
|
+
</ul>
|
|
10858
|
+
</div>
|
|
10859
|
+
` : ""}
|
|
10860
|
+
|
|
10861
|
+
<script>
|
|
10862
|
+
// Refresh media library after cleanup
|
|
10863
|
+
setTimeout(() => {
|
|
10864
|
+
window.location.href = '/admin/media?t=' + Date.now();
|
|
10865
|
+
}, 2500);
|
|
10866
|
+
</script>
|
|
10867
|
+
`);
|
|
10868
|
+
} catch (error) {
|
|
10869
|
+
console.error("Cleanup error:", error);
|
|
10870
|
+
return c.html(html`
|
|
10871
|
+
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
|
10872
|
+
Cleanup failed: ${error instanceof Error ? error.message : "Unknown error"}
|
|
10873
|
+
</div>
|
|
10874
|
+
`);
|
|
10875
|
+
}
|
|
10876
|
+
});
|
|
10762
10877
|
adminMediaRoutes.delete("/:id", async (c) => {
|
|
10763
10878
|
try {
|
|
10764
10879
|
const user = c.get("user");
|
|
@@ -16322,14 +16437,14 @@ router.get("/stats", async (c) => {
|
|
|
16322
16437
|
} catch (error) {
|
|
16323
16438
|
console.error("Error fetching users count:", error);
|
|
16324
16439
|
}
|
|
16325
|
-
const
|
|
16440
|
+
const html8 = renderStatsCards({
|
|
16326
16441
|
collections: collectionsCount,
|
|
16327
16442
|
contentItems: contentCount,
|
|
16328
16443
|
mediaFiles: mediaCount,
|
|
16329
16444
|
users: usersCount,
|
|
16330
16445
|
mediaSize
|
|
16331
16446
|
});
|
|
16332
|
-
return c.html(
|
|
16447
|
+
return c.html(html8);
|
|
16333
16448
|
} catch (error) {
|
|
16334
16449
|
console.error("Error fetching stats:", error);
|
|
16335
16450
|
return c.html('<div class="text-red-500">Failed to load statistics</div>');
|
|
@@ -16353,8 +16468,8 @@ router.get("/storage", async (c) => {
|
|
|
16353
16468
|
} catch (error) {
|
|
16354
16469
|
console.error("Error fetching media size:", error);
|
|
16355
16470
|
}
|
|
16356
|
-
const
|
|
16357
|
-
return c.html(
|
|
16471
|
+
const html8 = renderStorageUsage(databaseSize, mediaSize);
|
|
16472
|
+
return c.html(html8);
|
|
16358
16473
|
} catch (error) {
|
|
16359
16474
|
console.error("Error fetching storage usage:", error);
|
|
16360
16475
|
return c.html('<div class="text-red-500">Failed to load storage information</div>');
|
|
@@ -16403,12 +16518,12 @@ router.get("/recent-activity", async (c) => {
|
|
|
16403
16518
|
user: userName
|
|
16404
16519
|
};
|
|
16405
16520
|
});
|
|
16406
|
-
const
|
|
16407
|
-
return c.html(
|
|
16521
|
+
const html8 = renderRecentActivity(activities);
|
|
16522
|
+
return c.html(html8);
|
|
16408
16523
|
} catch (error) {
|
|
16409
16524
|
console.error("Error fetching recent activity:", error);
|
|
16410
|
-
const
|
|
16411
|
-
return c.html(
|
|
16525
|
+
const html8 = renderRecentActivity([]);
|
|
16526
|
+
return c.html(html8);
|
|
16412
16527
|
}
|
|
16413
16528
|
});
|
|
16414
16529
|
router.get("/api/metrics", async (c) => {
|
|
@@ -16421,7 +16536,7 @@ router.get("/api/metrics", async (c) => {
|
|
|
16421
16536
|
});
|
|
16422
16537
|
router.get("/system-status", async (c) => {
|
|
16423
16538
|
try {
|
|
16424
|
-
const
|
|
16539
|
+
const html8 = `
|
|
16425
16540
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
16426
16541
|
<div class="relative group">
|
|
16427
16542
|
<div class="absolute inset-0 bg-gradient-to-br from-blue-500/20 to-cyan-500/20 dark:from-blue-500/10 dark:to-cyan-500/10 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
@@ -16476,7 +16591,7 @@ router.get("/system-status", async (c) => {
|
|
|
16476
16591
|
</div>
|
|
16477
16592
|
</div>
|
|
16478
16593
|
`;
|
|
16479
|
-
return c.html(
|
|
16594
|
+
return c.html(html8);
|
|
16480
16595
|
} catch (error) {
|
|
16481
16596
|
console.error("Error fetching system status:", error);
|
|
16482
16597
|
return c.html('<div class="text-red-500">Failed to load system status</div>');
|
|
@@ -18232,31 +18347,52 @@ function renderSettingsPage(data) {
|
|
|
18232
18347
|
// Initialize tab-specific features on page load
|
|
18233
18348
|
const currentTab = '${activeTab}';
|
|
18234
18349
|
|
|
18235
|
-
function saveAllSettings() {
|
|
18350
|
+
async function saveAllSettings() {
|
|
18236
18351
|
// Collect all form data
|
|
18237
18352
|
const formData = new FormData();
|
|
18238
|
-
|
|
18239
|
-
// Get all form inputs
|
|
18240
|
-
document.querySelectorAll('input, select, textarea').forEach(input => {
|
|
18353
|
+
|
|
18354
|
+
// Get all form inputs in the settings content area
|
|
18355
|
+
document.querySelectorAll('#settings-content input, #settings-content select, #settings-content textarea').forEach(input => {
|
|
18241
18356
|
if (input.type === 'checkbox') {
|
|
18242
|
-
formData.append(input.name, input.checked);
|
|
18357
|
+
formData.append(input.name, input.checked ? 'true' : 'false');
|
|
18243
18358
|
} else if (input.name) {
|
|
18244
18359
|
formData.append(input.name, input.value);
|
|
18245
18360
|
}
|
|
18246
18361
|
});
|
|
18247
|
-
|
|
18362
|
+
|
|
18248
18363
|
// Show loading state
|
|
18249
18364
|
const saveBtn = document.querySelector('button[onclick="saveAllSettings()"]');
|
|
18250
18365
|
const originalText = saveBtn.innerHTML;
|
|
18251
|
-
saveBtn.innerHTML = 'Saving...';
|
|
18366
|
+
saveBtn.innerHTML = '<svg class="animate-spin -ml-0.5 mr-1.5 h-5 w-5 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>Saving...';
|
|
18252
18367
|
saveBtn.disabled = true;
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18368
|
+
|
|
18369
|
+
try {
|
|
18370
|
+
// Determine which endpoint to call based on current tab
|
|
18371
|
+
let endpoint = '/admin/settings/general';
|
|
18372
|
+
if (currentTab === 'general') {
|
|
18373
|
+
endpoint = '/admin/settings/general';
|
|
18374
|
+
}
|
|
18375
|
+
// Add more endpoints for other tabs when implemented
|
|
18376
|
+
|
|
18377
|
+
const response = await fetch(endpoint, {
|
|
18378
|
+
method: 'POST',
|
|
18379
|
+
body: formData
|
|
18380
|
+
});
|
|
18381
|
+
|
|
18382
|
+
const result = await response.json();
|
|
18383
|
+
|
|
18384
|
+
if (result.success) {
|
|
18385
|
+
showNotification(result.message || 'Settings saved successfully!', 'success');
|
|
18386
|
+
} else {
|
|
18387
|
+
showNotification(result.error || 'Failed to save settings', 'error');
|
|
18388
|
+
}
|
|
18389
|
+
} catch (error) {
|
|
18390
|
+
console.error('Error saving settings:', error);
|
|
18391
|
+
showNotification('Failed to save settings. Please try again.', 'error');
|
|
18392
|
+
} finally {
|
|
18256
18393
|
saveBtn.innerHTML = originalText;
|
|
18257
18394
|
saveBtn.disabled = false;
|
|
18258
|
-
|
|
18259
|
-
}, 1000);
|
|
18395
|
+
}
|
|
18260
18396
|
}
|
|
18261
18397
|
|
|
18262
18398
|
function resetSettings() {
|
|
@@ -19661,15 +19797,20 @@ function getMockSettings(user) {
|
|
|
19661
19797
|
adminSettingsRoutes.get("/", (c) => {
|
|
19662
19798
|
return c.redirect("/admin/settings/general");
|
|
19663
19799
|
});
|
|
19664
|
-
adminSettingsRoutes.get("/general", (c) => {
|
|
19800
|
+
adminSettingsRoutes.get("/general", async (c) => {
|
|
19665
19801
|
const user = c.get("user");
|
|
19802
|
+
const db = c.env.DB;
|
|
19803
|
+
const settingsService = new SettingsService(db);
|
|
19804
|
+
const generalSettings = await settingsService.getGeneralSettings(user?.email);
|
|
19805
|
+
const mockSettings = getMockSettings(user);
|
|
19806
|
+
mockSettings.general = generalSettings;
|
|
19666
19807
|
const pageData = {
|
|
19667
19808
|
user: user ? {
|
|
19668
19809
|
name: user.email,
|
|
19669
19810
|
email: user.email,
|
|
19670
19811
|
role: user.role
|
|
19671
19812
|
} : void 0,
|
|
19672
|
-
settings:
|
|
19813
|
+
settings: mockSettings,
|
|
19673
19814
|
activeTab: "general",
|
|
19674
19815
|
version: c.get("appVersion")
|
|
19675
19816
|
};
|
|
@@ -19950,28 +20091,55 @@ adminSettingsRoutes.post("/api/database-tools/truncate", async (c) => {
|
|
|
19950
20091
|
}, 500);
|
|
19951
20092
|
}
|
|
19952
20093
|
});
|
|
19953
|
-
adminSettingsRoutes.post("/", async (c) => {
|
|
20094
|
+
adminSettingsRoutes.post("/general", async (c) => {
|
|
19954
20095
|
try {
|
|
20096
|
+
const user = c.get("user");
|
|
20097
|
+
if (!user || user.role !== "admin") {
|
|
20098
|
+
return c.json({
|
|
20099
|
+
success: false,
|
|
20100
|
+
error: "Unauthorized. Admin access required."
|
|
20101
|
+
}, 403);
|
|
20102
|
+
}
|
|
19955
20103
|
const formData = await c.req.formData();
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
|
|
19962
|
-
|
|
19963
|
-
|
|
19964
|
-
|
|
19965
|
-
|
|
20104
|
+
const db = c.env.DB;
|
|
20105
|
+
const settingsService = new SettingsService(db);
|
|
20106
|
+
const settings = {
|
|
20107
|
+
siteName: formData.get("siteName"),
|
|
20108
|
+
siteDescription: formData.get("siteDescription"),
|
|
20109
|
+
adminEmail: formData.get("adminEmail"),
|
|
20110
|
+
timezone: formData.get("timezone"),
|
|
20111
|
+
language: formData.get("language"),
|
|
20112
|
+
maintenanceMode: formData.get("maintenanceMode") === "true"
|
|
20113
|
+
};
|
|
20114
|
+
if (!settings.siteName || !settings.siteDescription) {
|
|
20115
|
+
return c.json({
|
|
20116
|
+
success: false,
|
|
20117
|
+
error: "Site name and description are required"
|
|
20118
|
+
}, 400);
|
|
20119
|
+
}
|
|
20120
|
+
const success = await settingsService.saveGeneralSettings(settings);
|
|
20121
|
+
if (success) {
|
|
20122
|
+
return c.json({
|
|
20123
|
+
success: true,
|
|
20124
|
+
message: "General settings saved successfully!"
|
|
20125
|
+
});
|
|
20126
|
+
} else {
|
|
20127
|
+
return c.json({
|
|
20128
|
+
success: false,
|
|
20129
|
+
error: "Failed to save settings"
|
|
20130
|
+
}, 500);
|
|
20131
|
+
}
|
|
19966
20132
|
} catch (error) {
|
|
19967
|
-
console.error("Error saving settings:", error);
|
|
19968
|
-
return c.
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
`);
|
|
20133
|
+
console.error("Error saving general settings:", error);
|
|
20134
|
+
return c.json({
|
|
20135
|
+
success: false,
|
|
20136
|
+
error: "Failed to save settings. Please try again."
|
|
20137
|
+
}, 500);
|
|
19973
20138
|
}
|
|
19974
20139
|
});
|
|
20140
|
+
adminSettingsRoutes.post("/", async (c) => {
|
|
20141
|
+
return c.redirect("/admin/settings/general");
|
|
20142
|
+
});
|
|
19975
20143
|
|
|
19976
20144
|
// src/routes/index.ts
|
|
19977
20145
|
var ROUTES_INFO = {
|
|
@@ -20002,5 +20170,5 @@ var ROUTES_INFO = {
|
|
|
20002
20170
|
};
|
|
20003
20171
|
|
|
20004
20172
|
export { ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_faq_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, userRoutes };
|
|
20005
|
-
//# sourceMappingURL=chunk-
|
|
20006
|
-
//# sourceMappingURL=chunk-
|
|
20173
|
+
//# sourceMappingURL=chunk-JB2NUJJ5.js.map
|
|
20174
|
+
//# sourceMappingURL=chunk-JB2NUJJ5.js.map
|