@kyro-cms/admin 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { APIRoute } from "astro";
|
|
2
|
-
import { dataStore } from "../../../lib/dataStore";
|
|
3
|
-
|
|
4
|
-
export const DELETE: APIRoute = async ({ params }) => {
|
|
5
|
-
try {
|
|
6
|
-
const { id } = params;
|
|
7
|
-
if (!id) {
|
|
8
|
-
return new Response(JSON.stringify({ error: "ID is required" }), {
|
|
9
|
-
status: 400,
|
|
10
|
-
headers: { "Content-Type": "application/json" },
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
await dataStore.delete("_api_keys", id);
|
|
15
|
-
|
|
16
|
-
return new Response(JSON.stringify({ success: true }), {
|
|
17
|
-
status: 200,
|
|
18
|
-
headers: { "Content-Type": "application/json" },
|
|
19
|
-
});
|
|
20
|
-
} catch (error: any) {
|
|
21
|
-
return new Response(JSON.stringify({ error: error.message }), {
|
|
22
|
-
status: 500,
|
|
23
|
-
headers: { "Content-Type": "application/json" },
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { APIRoute } from "astro";
|
|
2
|
-
import { dataStore } from "../../../lib/dataStore";
|
|
3
|
-
|
|
4
|
-
export const GET: APIRoute = async () => {
|
|
5
|
-
try {
|
|
6
|
-
const keys = await dataStore.find("_api_keys", { limit: 100, page: 1 });
|
|
7
|
-
|
|
8
|
-
const docs = keys.docs.map((k: any) => ({
|
|
9
|
-
id: k.id,
|
|
10
|
-
name: k.name,
|
|
11
|
-
key: k.key,
|
|
12
|
-
keyPrefix: k.key?.substring(0, 8) || "",
|
|
13
|
-
createdAt: k.createdAt,
|
|
14
|
-
lastUsedAt: k.lastUsedAt,
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
return new Response(JSON.stringify(docs), {
|
|
18
|
-
status: 200,
|
|
19
|
-
headers: { "Content-Type": "application/json" },
|
|
20
|
-
});
|
|
21
|
-
} catch (error: any) {
|
|
22
|
-
return new Response(JSON.stringify({ error: error.message }), {
|
|
23
|
-
status: 500,
|
|
24
|
-
headers: { "Content-Type": "application/json" },
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export const POST: APIRoute = async ({ request }) => {
|
|
30
|
-
try {
|
|
31
|
-
const body = await request.json();
|
|
32
|
-
const { name } = body;
|
|
33
|
-
|
|
34
|
-
if (!name) {
|
|
35
|
-
return new Response(JSON.stringify({ error: "Name is required" }), {
|
|
36
|
-
status: 400,
|
|
37
|
-
headers: { "Content-Type": "application/json" },
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Generate API key
|
|
42
|
-
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
43
|
-
let suffix = "";
|
|
44
|
-
for (let i = 0; i < 28; i++) {
|
|
45
|
-
suffix += chars[Math.floor(Math.random() * chars.length)];
|
|
46
|
-
}
|
|
47
|
-
const key = `kyro_${suffix}`;
|
|
48
|
-
|
|
49
|
-
const now = new Date().toISOString();
|
|
50
|
-
const doc = await dataStore.create("_api_keys", {
|
|
51
|
-
name,
|
|
52
|
-
key,
|
|
53
|
-
keyPrefix: key.substring(0, 8),
|
|
54
|
-
createdAt: now,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return new Response(
|
|
58
|
-
JSON.stringify({
|
|
59
|
-
id: doc.id,
|
|
60
|
-
name: doc.name,
|
|
61
|
-
key: doc.key,
|
|
62
|
-
createdAt: doc.createdAt,
|
|
63
|
-
}),
|
|
64
|
-
{
|
|
65
|
-
status: 201,
|
|
66
|
-
headers: { "Content-Type": "application/json" },
|
|
67
|
-
},
|
|
68
|
-
);
|
|
69
|
-
} catch (error: any) {
|
|
70
|
-
return new Response(JSON.stringify({ error: error.message }), {
|
|
71
|
-
status: 500,
|
|
72
|
-
headers: { "Content-Type": "application/json" },
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
};
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import type { APIRoute } from "astro";
|
|
2
|
-
import { getMediaService } from "../../../lib/MediaService";
|
|
3
|
-
import { MediaService } from "@kyro-cms/core";
|
|
4
|
-
import { getStorageConfig, constructMediaUrl } from "../../../lib/storage";
|
|
5
|
-
import { getDatabaseConfig } from "../../../lib/db";
|
|
6
|
-
import path from "path";
|
|
7
|
-
|
|
8
|
-
export const GET: APIRoute = async ({ params }) => {
|
|
9
|
-
try {
|
|
10
|
-
const mediaService = await getMediaService();
|
|
11
|
-
const { id } = params;
|
|
12
|
-
|
|
13
|
-
if (!id) {
|
|
14
|
-
return new Response(JSON.stringify({ error: "No file ID provided" }), {
|
|
15
|
-
status: 400,
|
|
16
|
-
headers: { "Content-Type": "application/json" },
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const doc = await mediaService.findById(id);
|
|
21
|
-
if (!doc) {
|
|
22
|
-
return new Response(JSON.stringify({ error: "Media not found" }), {
|
|
23
|
-
status: 404,
|
|
24
|
-
headers: { "Content-Type": "application/json" },
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Compute URLs dynamically from current storage settings
|
|
29
|
-
const storageConfig = await getStorageConfig();
|
|
30
|
-
const isLocalStorage = storageConfig.provider === "local";
|
|
31
|
-
const isCloudStorage = !isLocalStorage;
|
|
32
|
-
|
|
33
|
-
// For cloud storage, use stored URL from DB; for local, construct from config
|
|
34
|
-
const mediaUrl = isCloudStorage
|
|
35
|
-
? doc.url || (await constructMediaUrl(doc.filename, null))
|
|
36
|
-
: await constructMediaUrl(doc.filename, doc.folder);
|
|
37
|
-
|
|
38
|
-
// For local storage use resize API, for cloud use direct URL
|
|
39
|
-
let thumbnailUrl: string | undefined;
|
|
40
|
-
if (doc.mimeType?.startsWith("image/")) {
|
|
41
|
-
if (isLocalStorage) {
|
|
42
|
-
thumbnailUrl = `/api/media/resize?url=${encodeURIComponent(mediaUrl)}&w=400&h=400`;
|
|
43
|
-
} else {
|
|
44
|
-
thumbnailUrl = mediaUrl;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return new Response(
|
|
49
|
-
JSON.stringify({
|
|
50
|
-
id: doc.id,
|
|
51
|
-
title: doc.title || doc.filename,
|
|
52
|
-
filename: doc.filename,
|
|
53
|
-
originalName: doc.originalName,
|
|
54
|
-
url: mediaUrl,
|
|
55
|
-
thumbnailUrl,
|
|
56
|
-
type: doc.mimeType?.split("/")[0] || "other",
|
|
57
|
-
mimeType: doc.mimeType,
|
|
58
|
-
fileSize: doc.fileSize,
|
|
59
|
-
width: doc.width,
|
|
60
|
-
height: doc.height,
|
|
61
|
-
folder: doc.folder,
|
|
62
|
-
alt: doc.alt,
|
|
63
|
-
caption: doc.caption,
|
|
64
|
-
createdAt: doc.createdAt,
|
|
65
|
-
updatedAt: doc.updatedAt,
|
|
66
|
-
}),
|
|
67
|
-
{
|
|
68
|
-
status: 200,
|
|
69
|
-
headers: { "Content-Type": "application/json" },
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
console.error("Get media error:", error);
|
|
74
|
-
return new Response(JSON.stringify({ error: "Failed to fetch media" }), {
|
|
75
|
-
status: 500,
|
|
76
|
-
headers: { "Content-Type": "application/json" },
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export const PATCH: APIRoute = async ({ params, request }) => {
|
|
82
|
-
try {
|
|
83
|
-
const mediaService = await getMediaService();
|
|
84
|
-
const { id } = params;
|
|
85
|
-
|
|
86
|
-
if (!id) {
|
|
87
|
-
return new Response(JSON.stringify({ error: "No file ID provided" }), {
|
|
88
|
-
status: 400,
|
|
89
|
-
headers: { "Content-Type": "application/json" },
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const body = await request.json();
|
|
94
|
-
|
|
95
|
-
// Fetch current record to compare
|
|
96
|
-
const current = await mediaService.findById(id);
|
|
97
|
-
if (!current) {
|
|
98
|
-
return new Response(JSON.stringify({ error: "Media not found" }), {
|
|
99
|
-
status: 404,
|
|
100
|
-
headers: { "Content-Type": "application/json" },
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const updateData: any = {
|
|
105
|
-
alt: body.alt,
|
|
106
|
-
caption: body.caption,
|
|
107
|
-
title: body.title,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// If the title changed, rename the physical file and update url/filename
|
|
111
|
-
const newTitle: string | undefined = body.title;
|
|
112
|
-
if (
|
|
113
|
-
newTitle &&
|
|
114
|
-
newTitle !== current.title &&
|
|
115
|
-
newTitle !== current.filename
|
|
116
|
-
) {
|
|
117
|
-
const storageConfig = await getStorageConfig();
|
|
118
|
-
const isLocalStorage = storageConfig.provider === "local";
|
|
119
|
-
|
|
120
|
-
// Preserve original extension, apply new name
|
|
121
|
-
const ext = path.extname(current.filename);
|
|
122
|
-
const sanitized = newTitle
|
|
123
|
-
.trim()
|
|
124
|
-
.toLowerCase()
|
|
125
|
-
.replace(/[^a-z0-9._-]/g, "_")
|
|
126
|
-
.replace(/_+/g, "_");
|
|
127
|
-
const newFilename = sanitized.endsWith(ext)
|
|
128
|
-
? sanitized
|
|
129
|
-
: `${sanitized}${ext}`;
|
|
130
|
-
|
|
131
|
-
const folder = current.folder ? `${current.folder}/` : "";
|
|
132
|
-
const newKey = `${folder}${newFilename}`;
|
|
133
|
-
|
|
134
|
-
if (isLocalStorage) {
|
|
135
|
-
const uploadDir = storageConfig.uploadDir || "./public/uploads";
|
|
136
|
-
const baseUrl = storageConfig.baseUrl || "/uploads";
|
|
137
|
-
const folderPath = folder.replace("/", "");
|
|
138
|
-
|
|
139
|
-
const oldPhysical = path.join(uploadDir, folderPath, current.filename);
|
|
140
|
-
const newPhysical = path.join(uploadDir, folderPath, newFilename);
|
|
141
|
-
|
|
142
|
-
const fs = await import("fs/promises");
|
|
143
|
-
try {
|
|
144
|
-
await fs.rename(oldPhysical, newPhysical);
|
|
145
|
-
} catch (e: any) {
|
|
146
|
-
console.warn("[rename] Could not rename physical file:", e.message);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const normalizedBase = baseUrl.endsWith("/")
|
|
150
|
-
? baseUrl.slice(0, -1)
|
|
151
|
-
: baseUrl;
|
|
152
|
-
updateData.url = `${normalizedBase}/${folder}${newFilename}`;
|
|
153
|
-
} else {
|
|
154
|
-
// Cloud storage - use MediaService rename
|
|
155
|
-
try {
|
|
156
|
-
const dbConfig = getDatabaseConfig();
|
|
157
|
-
let db: any = null;
|
|
158
|
-
|
|
159
|
-
if (dbConfig.type === "sqlite") {
|
|
160
|
-
const Database = (await import("better-sqlite3")).default;
|
|
161
|
-
db = new Database(dbConfig.contentDbPath || "./data/content.db");
|
|
162
|
-
} else if (dbConfig.type === "postgres") {
|
|
163
|
-
const { Pool } = await import("pg");
|
|
164
|
-
db = new Pool({ connectionString: dbConfig.connectionString });
|
|
165
|
-
} else if (dbConfig.type === "mysql") {
|
|
166
|
-
const mysql = await import("mysql2/promise");
|
|
167
|
-
db = await mysql.createPool({ uri: dbConfig.connectionString });
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Transform storageConfig to have 'type' field (for resolveProviderWithConfig)
|
|
171
|
-
const cloudStorageConfig = {
|
|
172
|
-
type: storageConfig.provider,
|
|
173
|
-
[storageConfig.provider]: storageConfig.config,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const coreMediaService = await MediaService.init(db, {
|
|
177
|
-
dialect: dbConfig.type as any,
|
|
178
|
-
storageConfig: cloudStorageConfig,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const renamed = await coreMediaService.rename(id, newKey);
|
|
182
|
-
if (renamed) {
|
|
183
|
-
updateData.url = renamed.url;
|
|
184
|
-
updateData.filename = renamed.filename;
|
|
185
|
-
if (renamed.thumbnailUrl) {
|
|
186
|
-
updateData.thumbnailUrl = renamed.thumbnailUrl;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (db && dbConfig.type === "sqlite") db.close();
|
|
191
|
-
} catch (e: any) {
|
|
192
|
-
console.error(
|
|
193
|
-
"[rename] Cloud storage rename error:",
|
|
194
|
-
e.message,
|
|
195
|
-
e.stack,
|
|
196
|
-
);
|
|
197
|
-
// Continue anyway - still update DB metadata
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (!updateData.filename) {
|
|
202
|
-
updateData.filename = newFilename;
|
|
203
|
-
}
|
|
204
|
-
if (!updateData.url) {
|
|
205
|
-
const { baseUrl } = await getStorageConfig();
|
|
206
|
-
const normalizedBase = baseUrl.endsWith("/")
|
|
207
|
-
? baseUrl.slice(0, -1)
|
|
208
|
-
: baseUrl;
|
|
209
|
-
updateData.url = `${normalizedBase}/${folder}${newFilename}`;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const updated = await mediaService.update(id, updateData);
|
|
214
|
-
|
|
215
|
-
return new Response(JSON.stringify({ success: true, media: updated }), {
|
|
216
|
-
status: 200,
|
|
217
|
-
headers: { "Content-Type": "application/json" },
|
|
218
|
-
});
|
|
219
|
-
} catch (error) {
|
|
220
|
-
console.error("Patch media error:", error);
|
|
221
|
-
return new Response(
|
|
222
|
-
JSON.stringify({ error: "Failed to update metadata" }),
|
|
223
|
-
{
|
|
224
|
-
status: 500,
|
|
225
|
-
headers: { "Content-Type": "application/json" },
|
|
226
|
-
},
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
export const DELETE: APIRoute = async ({ params }) => {
|
|
232
|
-
try {
|
|
233
|
-
const mediaService = await getMediaService();
|
|
234
|
-
const { id } = params;
|
|
235
|
-
|
|
236
|
-
if (!id) {
|
|
237
|
-
return new Response(JSON.stringify({ error: "No file ID provided" }), {
|
|
238
|
-
status: 400,
|
|
239
|
-
headers: { "Content-Type": "application/json" },
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Get the media item first to delete from storage
|
|
244
|
-
const item = await mediaService.findById(id);
|
|
245
|
-
if (!item) {
|
|
246
|
-
return new Response(JSON.stringify({ error: "Media not found" }), {
|
|
247
|
-
status: 404,
|
|
248
|
-
headers: { "Content-Type": "application/json" },
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Delete from cloud storage if not local
|
|
253
|
-
const storageConfig = await getStorageConfig();
|
|
254
|
-
if (storageConfig.provider !== "local") {
|
|
255
|
-
try {
|
|
256
|
-
const dbConfig = getDatabaseConfig();
|
|
257
|
-
let db: any = null;
|
|
258
|
-
|
|
259
|
-
if (dbConfig.type === "sqlite") {
|
|
260
|
-
const Database = (await import("better-sqlite3")).default;
|
|
261
|
-
db = new Database(dbConfig.contentDbPath || "./data/content.db");
|
|
262
|
-
} else if (dbConfig.type === "postgres") {
|
|
263
|
-
const { Pool } = await import("pg");
|
|
264
|
-
db = new Pool({ connectionString: dbConfig.connectionString });
|
|
265
|
-
} else if (dbConfig.type === "mysql") {
|
|
266
|
-
const mysql = await import("mysql2/promise");
|
|
267
|
-
db = await mysql.createPool({ uri: dbConfig.connectionString });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Transform storageConfig to have 'type' field
|
|
271
|
-
const cloudStorageConfig = {
|
|
272
|
-
type: storageConfig.provider,
|
|
273
|
-
[storageConfig.provider]: storageConfig.config,
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const coreMediaService = await MediaService.init(db, {
|
|
277
|
-
dialect: dbConfig.type as any,
|
|
278
|
-
storageConfig: cloudStorageConfig,
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Delete the file from storage
|
|
282
|
-
await coreMediaService.deleteFile(item.url);
|
|
283
|
-
if (item.thumbnailUrl) {
|
|
284
|
-
try {
|
|
285
|
-
await coreMediaService.deleteFile(item.thumbnailUrl);
|
|
286
|
-
} catch {}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (db && dbConfig.type === "sqlite") db.close();
|
|
290
|
-
} catch (e: any) {
|
|
291
|
-
console.error("[delete] Cloud storage delete error:", e.message);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Delete from database
|
|
296
|
-
await mediaService.delete(id);
|
|
297
|
-
|
|
298
|
-
return new Response(JSON.stringify({ success: true }), {
|
|
299
|
-
status: 200,
|
|
300
|
-
headers: { "Content-Type": "application/json" },
|
|
301
|
-
});
|
|
302
|
-
} catch (error) {
|
|
303
|
-
console.error("Delete media error:", error);
|
|
304
|
-
return new Response(JSON.stringify({ error: "Failed to delete file" }), {
|
|
305
|
-
status: 500,
|
|
306
|
-
headers: { "Content-Type": "application/json" },
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
};
|