@kyro-cms/admin 0.3.1 → 0.3.4
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
package/src/lib/storage.ts
DELETED
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { getDatabaseConfig } from "./db";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Extract the Public Dev URL ID from either a full URL or just the ID.
|
|
6
|
-
* Handles formats like:
|
|
7
|
-
* - https://bucket.pub-xxx.r2.dev -> pub-xxx
|
|
8
|
-
* - pub-xxx -> pub-xxx
|
|
9
|
-
* - empty/undefined -> ""
|
|
10
|
-
*/
|
|
11
|
-
function extractPublicDevUrlId(url?: string): string {
|
|
12
|
-
if (!url) return "";
|
|
13
|
-
if (url.startsWith("pub-")) return url; // Already just the ID
|
|
14
|
-
|
|
15
|
-
// Extract from URL like https://bucket.pub-xxx.r2.dev
|
|
16
|
-
const match = url.match(/pub-[a-zA-Z0-9]+/i);
|
|
17
|
-
return match ? match[0] : "";
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type StorageProviderType =
|
|
21
|
-
| "local"
|
|
22
|
-
| "aws"
|
|
23
|
-
| "r2"
|
|
24
|
-
| "gcs"
|
|
25
|
-
| "digitalocean"
|
|
26
|
-
| "backblaze"
|
|
27
|
-
| "wasabi"
|
|
28
|
-
| "bunny"
|
|
29
|
-
| "cloudinary"
|
|
30
|
-
| "imgix"
|
|
31
|
-
| "ftp";
|
|
32
|
-
|
|
33
|
-
export interface StorageConfig {
|
|
34
|
-
/** The storage provider type */
|
|
35
|
-
provider: StorageProviderType;
|
|
36
|
-
/** The base URL for accessing files */
|
|
37
|
-
baseUrl: string;
|
|
38
|
-
/** The filesystem directory (for local storage only) */
|
|
39
|
-
uploadDir?: string;
|
|
40
|
-
/** Provider-specific config */
|
|
41
|
-
config: {
|
|
42
|
-
bucket?: string;
|
|
43
|
-
region?: string;
|
|
44
|
-
accountId?: string;
|
|
45
|
-
accessKeyId?: string;
|
|
46
|
-
secretAccessKey?: string;
|
|
47
|
-
cdnUrl?: string;
|
|
48
|
-
prefix?: string;
|
|
49
|
-
publicDevUrl?: string;
|
|
50
|
-
cloudName?: string;
|
|
51
|
-
apiKey?: string;
|
|
52
|
-
apiSecret?: string;
|
|
53
|
-
folder?: string;
|
|
54
|
-
uploadPreset?: string;
|
|
55
|
-
domain?: string;
|
|
56
|
-
signKey?: string;
|
|
57
|
-
host?: string;
|
|
58
|
-
port?: number;
|
|
59
|
-
user?: string;
|
|
60
|
-
password?: string;
|
|
61
|
-
secure?: boolean;
|
|
62
|
-
storageZone?: string;
|
|
63
|
-
projectId?: string;
|
|
64
|
-
type?: string;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
interface RawStorageSettings {
|
|
69
|
-
provider?: string;
|
|
70
|
-
local?: {
|
|
71
|
-
uploadDir?: string;
|
|
72
|
-
baseUrl?: string;
|
|
73
|
-
};
|
|
74
|
-
aws?: Record<string, any>;
|
|
75
|
-
r2?: Record<string, any>;
|
|
76
|
-
gcs?: Record<string, any>;
|
|
77
|
-
digitalocean?: Record<string, any>;
|
|
78
|
-
backblaze?: Record<string, any>;
|
|
79
|
-
wasabi?: Record<string, any>;
|
|
80
|
-
bunny?: Record<string, any>;
|
|
81
|
-
cloudinary?: Record<string, any>;
|
|
82
|
-
imgix?: Record<string, any>;
|
|
83
|
-
ftp?: Record<string, any>;
|
|
84
|
-
limits?: Record<string, any>;
|
|
85
|
-
[key: string]: any;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Read the complete storage configuration from the globals table.
|
|
90
|
-
*/
|
|
91
|
-
export async function getStorageConfig(): Promise<StorageConfig> {
|
|
92
|
-
const dbConfig = getDatabaseConfig();
|
|
93
|
-
let rawSettings: RawStorageSettings = { provider: "local" };
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
if (dbConfig.type === "sqlite") {
|
|
97
|
-
const Database = (await import("better-sqlite3")).default;
|
|
98
|
-
const db = new Database(dbConfig.contentDbPath || "./data/content.db");
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const row = db
|
|
102
|
-
.prepare("SELECT data FROM globals WHERE slug = ?")
|
|
103
|
-
.get("storage-settings") as { data: string } | undefined;
|
|
104
|
-
|
|
105
|
-
if (row) {
|
|
106
|
-
rawSettings =
|
|
107
|
-
typeof row.data === "string" ? JSON.parse(row.data) : row.data;
|
|
108
|
-
}
|
|
109
|
-
} finally {
|
|
110
|
-
db.close();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
} catch {
|
|
114
|
-
// Use defaults if anything fails
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const provider = (rawSettings.provider || "local") as StorageProviderType;
|
|
118
|
-
|
|
119
|
-
// Build config based on provider type
|
|
120
|
-
const config: StorageConfig = {
|
|
121
|
-
provider,
|
|
122
|
-
baseUrl: "",
|
|
123
|
-
config: {},
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
switch (provider) {
|
|
127
|
-
case "local": {
|
|
128
|
-
const localConfig = rawSettings.local || {};
|
|
129
|
-
const uploadDirRaw = localConfig.uploadDir || "./public/uploads";
|
|
130
|
-
const baseUrlRaw = localConfig.baseUrl || "/uploads";
|
|
131
|
-
|
|
132
|
-
// Resolve uploadDir
|
|
133
|
-
let uploadDir: string;
|
|
134
|
-
if (path.isAbsolute(uploadDirRaw)) {
|
|
135
|
-
uploadDir = uploadDirRaw;
|
|
136
|
-
} else if (uploadDirRaw.includes("/") || uploadDirRaw.includes("\\")) {
|
|
137
|
-
uploadDir = path.resolve(process.cwd(), uploadDirRaw);
|
|
138
|
-
} else {
|
|
139
|
-
uploadDir = path.join(process.cwd(), "public", uploadDirRaw);
|
|
140
|
-
}
|
|
141
|
-
config.uploadDir = uploadDir;
|
|
142
|
-
|
|
143
|
-
// Resolve baseUrl
|
|
144
|
-
config.baseUrl = baseUrlRaw.startsWith("/")
|
|
145
|
-
? baseUrlRaw
|
|
146
|
-
: `/${baseUrlRaw}`;
|
|
147
|
-
if (config.baseUrl.length > 1) {
|
|
148
|
-
config.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
149
|
-
}
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
case "aws": {
|
|
154
|
-
const awsConfig = rawSettings.aws || {};
|
|
155
|
-
config.baseUrl =
|
|
156
|
-
awsConfig.cdnUrl ||
|
|
157
|
-
`https://${awsConfig.bucket}.s3.${awsConfig.region || "us-east-1"}.amazonaws.com`;
|
|
158
|
-
config.config = {
|
|
159
|
-
bucket: awsConfig.bucket,
|
|
160
|
-
region: awsConfig.region || "us-east-1",
|
|
161
|
-
accessKeyId: awsConfig.accessKeyId,
|
|
162
|
-
secretAccessKey: awsConfig.secretAccessKey,
|
|
163
|
-
cdnUrl: awsConfig.cdnUrl,
|
|
164
|
-
prefix: awsConfig.prefix,
|
|
165
|
-
};
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
case "r2": {
|
|
170
|
-
const r2Config = rawSettings.r2 || {};
|
|
171
|
-
// Priority: cdnUrl > publicDevUrl > accountId-based URL
|
|
172
|
-
let baseUrl: string;
|
|
173
|
-
if (r2Config.cdnUrl) {
|
|
174
|
-
baseUrl = r2Config.cdnUrl.replace(/\/$/, "");
|
|
175
|
-
} else if (r2Config.publicDevUrl) {
|
|
176
|
-
// Handle both full URL and just the ID
|
|
177
|
-
const pubId = extractPublicDevUrlId(r2Config.publicDevUrl);
|
|
178
|
-
baseUrl = pubId ? `https://${pubId}.r2.dev` : "";
|
|
179
|
-
} else if (r2Config.accountId) {
|
|
180
|
-
baseUrl = `https://${r2Config.bucket}.${r2Config.accountId}.r2.cloudflarestorage.com`;
|
|
181
|
-
} else {
|
|
182
|
-
baseUrl = "";
|
|
183
|
-
}
|
|
184
|
-
config.baseUrl = baseUrl;
|
|
185
|
-
config.config = {
|
|
186
|
-
bucket: r2Config.bucket,
|
|
187
|
-
accountId: r2Config.accountId,
|
|
188
|
-
publicDevUrl: extractPublicDevUrlId(r2Config.publicDevUrl),
|
|
189
|
-
accessKeyId: r2Config.accessKeyId,
|
|
190
|
-
secretAccessKey: r2Config.secretAccessKey,
|
|
191
|
-
cdnUrl: r2Config.cdnUrl,
|
|
192
|
-
prefix: r2Config.prefix,
|
|
193
|
-
};
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
case "gcs": {
|
|
198
|
-
const gcsConfig = rawSettings.gcs || {};
|
|
199
|
-
config.baseUrl =
|
|
200
|
-
gcsConfig.cdnUrl ||
|
|
201
|
-
`https://storage.googleapis.com/${gcsConfig.bucket}`;
|
|
202
|
-
config.config = {
|
|
203
|
-
bucket: gcsConfig.bucket,
|
|
204
|
-
projectId: gcsConfig.projectId,
|
|
205
|
-
accessKeyId: gcsConfig.clientEmail,
|
|
206
|
-
secretAccessKey: gcsConfig.privateKey,
|
|
207
|
-
cdnUrl: gcsConfig.cdnUrl,
|
|
208
|
-
prefix: gcsConfig.prefix,
|
|
209
|
-
};
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
case "digitalocean": {
|
|
214
|
-
const doConfig = rawSettings.digitalocean || {};
|
|
215
|
-
const region = doConfig.region || "nyc3";
|
|
216
|
-
config.baseUrl =
|
|
217
|
-
doConfig.cdnUrl ||
|
|
218
|
-
`https://${doConfig.bucket}.${region}.cdn.digitaloceanspaces.com`;
|
|
219
|
-
config.config = {
|
|
220
|
-
bucket: doConfig.bucket,
|
|
221
|
-
region,
|
|
222
|
-
accessKeyId: doConfig.accessKeyId,
|
|
223
|
-
secretAccessKey: doConfig.secretAccessKey,
|
|
224
|
-
cdnUrl: doConfig.cdnUrl,
|
|
225
|
-
prefix: doConfig.prefix,
|
|
226
|
-
};
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
case "backblaze": {
|
|
231
|
-
const b2Config = rawSettings.backblaze || {};
|
|
232
|
-
config.baseUrl = b2Config.cdnUrl || `https://f000.backblazeb2.com`;
|
|
233
|
-
config.config = {
|
|
234
|
-
bucket: b2Config.bucket,
|
|
235
|
-
accountId: b2Config.accountId,
|
|
236
|
-
accessKeyId: b2Config.applicationKeyId,
|
|
237
|
-
secretAccessKey: b2Config.applicationKey,
|
|
238
|
-
cdnUrl: b2Config.cdnUrl,
|
|
239
|
-
prefix: b2Config.prefix,
|
|
240
|
-
};
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
case "wasabi": {
|
|
245
|
-
const wasabiConfig = rawSettings.wasabi || {};
|
|
246
|
-
const region = wasabiConfig.region || "us-east-1";
|
|
247
|
-
config.baseUrl =
|
|
248
|
-
wasabiConfig.cdnUrl || `https://s3.${region}.wasabisys.com`;
|
|
249
|
-
config.config = {
|
|
250
|
-
bucket: wasabiConfig.bucket,
|
|
251
|
-
region,
|
|
252
|
-
accessKeyId: wasabiConfig.accessKeyId,
|
|
253
|
-
secretAccessKey: wasabiConfig.secretAccessKey,
|
|
254
|
-
cdnUrl: wasabiConfig.cdnUrl,
|
|
255
|
-
prefix: wasabiConfig.prefix,
|
|
256
|
-
};
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
case "bunny": {
|
|
261
|
-
const bunnyConfig = rawSettings.bunny || {};
|
|
262
|
-
config.baseUrl =
|
|
263
|
-
bunnyConfig.cdnUrl || `https://${bunnyConfig.storageZone}.b-cdn.net`;
|
|
264
|
-
config.config = {
|
|
265
|
-
storageZone: bunnyConfig.storageZone,
|
|
266
|
-
apiKey: bunnyConfig.apiKey,
|
|
267
|
-
cdnUrl: bunnyConfig.cdnUrl,
|
|
268
|
-
prefix: bunnyConfig.prefix,
|
|
269
|
-
};
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
case "cloudinary": {
|
|
274
|
-
const cloudinaryConfig = rawSettings.cloudinary || {};
|
|
275
|
-
config.baseUrl = `https://res.cloudinary.com/${cloudinaryConfig.cloudName}/image/upload`;
|
|
276
|
-
config.config = {
|
|
277
|
-
cloudName: cloudinaryConfig.cloudName,
|
|
278
|
-
apiKey: cloudinaryConfig.apiKey,
|
|
279
|
-
apiSecret: cloudinaryConfig.apiSecret,
|
|
280
|
-
folder: cloudinaryConfig.folder,
|
|
281
|
-
uploadPreset: cloudinaryConfig.uploadPreset,
|
|
282
|
-
};
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
case "imgix": {
|
|
287
|
-
const imgixConfig = rawSettings.imgix || {};
|
|
288
|
-
config.baseUrl = `https://${imgixConfig.domain}`;
|
|
289
|
-
config.config = {
|
|
290
|
-
domain: imgixConfig.domain,
|
|
291
|
-
signKey: imgixConfig.signKey,
|
|
292
|
-
};
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
case "ftp": {
|
|
297
|
-
const ftpConfig = rawSettings.ftp || {};
|
|
298
|
-
config.baseUrl = ftpConfig.baseUrl || "";
|
|
299
|
-
config.config = {
|
|
300
|
-
host: ftpConfig.host,
|
|
301
|
-
port: ftpConfig.port || 22,
|
|
302
|
-
user: ftpConfig.user,
|
|
303
|
-
password: ftpConfig.password,
|
|
304
|
-
secure: ftpConfig.secure,
|
|
305
|
-
prefix: ftpConfig.prefix,
|
|
306
|
-
type: "sftp",
|
|
307
|
-
};
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
default:
|
|
312
|
-
// Fallback to local
|
|
313
|
-
config.provider = "local";
|
|
314
|
-
config.uploadDir = path.join(process.cwd(), "public", "uploads");
|
|
315
|
-
config.baseUrl = "/uploads";
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return config;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Construct a media URL from filename and optional folder using current storage settings.
|
|
323
|
-
* For cloud providers, this includes the full base URL. For local, it uses relative path.
|
|
324
|
-
*/
|
|
325
|
-
export async function constructMediaUrl(
|
|
326
|
-
filename: string,
|
|
327
|
-
folder?: string | null,
|
|
328
|
-
): Promise<string> {
|
|
329
|
-
const config = await getStorageConfig();
|
|
330
|
-
return constructMediaUrlSync(config.baseUrl, filename, folder);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Construct a media URL synchronously.
|
|
335
|
-
*/
|
|
336
|
-
export function constructMediaUrlSync(
|
|
337
|
-
baseUrl: string,
|
|
338
|
-
filename: string,
|
|
339
|
-
folder?: string | null,
|
|
340
|
-
): string {
|
|
341
|
-
// Cloudinary stores full URL in the database - just return it if baseUrl matches
|
|
342
|
-
// The filename for cloud storage already contains folder path
|
|
343
|
-
if (baseUrl.startsWith("http") && filename.startsWith("http")) {
|
|
344
|
-
// Already a full URL (e.g., from Cloudinary upload response)
|
|
345
|
-
return filename;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Ensure baseUrl has trailing slash for proper URL construction
|
|
349
|
-
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
|
|
350
|
-
const folderPrefix = folder ? `${folder}/` : "";
|
|
351
|
-
|
|
352
|
-
// For local/relative URLs
|
|
353
|
-
return `${normalizedBaseUrl}${folderPrefix}${filename}`;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Get provider display name
|
|
358
|
-
*/
|
|
359
|
-
export function getProviderDisplayName(provider: string): string {
|
|
360
|
-
const names: Record<string, string> = {
|
|
361
|
-
local: "Local Server",
|
|
362
|
-
aws: "AWS S3",
|
|
363
|
-
r2: "Cloudflare R2",
|
|
364
|
-
gcs: "Google Cloud Storage",
|
|
365
|
-
digitalocean: "DigitalOcean Spaces",
|
|
366
|
-
backblaze: "Backblaze B2",
|
|
367
|
-
wasabi: "Wasabi",
|
|
368
|
-
bunny: "Bunny.net",
|
|
369
|
-
cloudinary: "Cloudinary",
|
|
370
|
-
imgix: "Imgix",
|
|
371
|
-
ftp: "FTP",
|
|
372
|
-
};
|
|
373
|
-
return names[provider] || provider;
|
|
374
|
-
}
|
package/src/lib/store.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { create } from 'zustand';
|
|
2
|
-
|
|
3
|
-
interface EditorState {
|
|
4
|
-
editor: any;
|
|
5
|
-
setEditor: (editor: any) => void;
|
|
6
|
-
|
|
7
|
-
blockDrawerOpen: boolean;
|
|
8
|
-
openBlockDrawer: (options?: { targetColumn?: number; targetNodePos?: number }) => void;
|
|
9
|
-
closeBlockDrawer: () => void;
|
|
10
|
-
toggleBlockDrawer: () => void;
|
|
11
|
-
|
|
12
|
-
selectedBlock: string | null;
|
|
13
|
-
setSelectedBlock: (block: string | null) => void;
|
|
14
|
-
|
|
15
|
-
pendingInsertPos: number | null;
|
|
16
|
-
pendingTargetColumn: number | null;
|
|
17
|
-
pendingTargetStack: number | null;
|
|
18
|
-
pendingTargetGroup: number | null;
|
|
19
|
-
pendingTargetCard: number | null;
|
|
20
|
-
pendingTargetRepeater: number | null;
|
|
21
|
-
setPendingInsert: (pos: number | null, column?: number | null) => void;
|
|
22
|
-
clearPendingTargets: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const useEditorStore = create<EditorState>((set) => ({
|
|
26
|
-
editor: null,
|
|
27
|
-
setEditor: (editor) => set({ editor }),
|
|
28
|
-
|
|
29
|
-
blockDrawerOpen: false,
|
|
30
|
-
openBlockDrawer: (options) => set({
|
|
31
|
-
blockDrawerOpen: true,
|
|
32
|
-
pendingTargetColumn: options?.targetColumn ?? null
|
|
33
|
-
}),
|
|
34
|
-
closeBlockDrawer: () => set({
|
|
35
|
-
blockDrawerOpen: false,
|
|
36
|
-
pendingTargetColumn: null,
|
|
37
|
-
pendingInsertPos: null,
|
|
38
|
-
pendingTargetStack: null,
|
|
39
|
-
pendingTargetGroup: null,
|
|
40
|
-
pendingTargetCard: null,
|
|
41
|
-
pendingTargetRepeater: null,
|
|
42
|
-
}),
|
|
43
|
-
toggleBlockDrawer: () => set((state) => ({ blockDrawerOpen: !state.blockDrawerOpen })),
|
|
44
|
-
|
|
45
|
-
selectedBlock: null,
|
|
46
|
-
setSelectedBlock: (block) => set({ selectedBlock: block }),
|
|
47
|
-
|
|
48
|
-
pendingInsertPos: null,
|
|
49
|
-
pendingTargetColumn: null,
|
|
50
|
-
pendingTargetStack: null,
|
|
51
|
-
pendingTargetGroup: null,
|
|
52
|
-
pendingTargetCard: null,
|
|
53
|
-
pendingTargetRepeater: null,
|
|
54
|
-
setPendingInsert: (pos, column) => set({
|
|
55
|
-
pendingInsertPos: pos,
|
|
56
|
-
pendingTargetColumn: column ?? null
|
|
57
|
-
}),
|
|
58
|
-
clearPendingTargets: () => set({
|
|
59
|
-
pendingTargetColumn: null,
|
|
60
|
-
pendingTargetStack: null,
|
|
61
|
-
pendingTargetGroup: null,
|
|
62
|
-
pendingTargetCard: null,
|
|
63
|
-
pendingTargetRepeater: null,
|
|
64
|
-
}),
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
interface UIState {
|
|
68
|
-
sidebarOpen: boolean;
|
|
69
|
-
toggleSidebar: () => void;
|
|
70
|
-
setSidebarOpen: (open: boolean) => void;
|
|
71
|
-
|
|
72
|
-
activeModal: string | null;
|
|
73
|
-
openModal: (modal: string) => void;
|
|
74
|
-
closeModal: () => void;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const useUIStore = create<UIState>((set) => ({
|
|
78
|
-
sidebarOpen: true,
|
|
79
|
-
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
|
80
|
-
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
|
81
|
-
|
|
82
|
-
activeModal: null,
|
|
83
|
-
openModal: (modal) => set({ activeModal: modal }),
|
|
84
|
-
closeModal: () => set({ activeModal: null }),
|
|
85
|
-
}));
|
package/src/middleware.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import type { MiddlewareHandler } from "astro";
|
|
2
|
-
import jwt from "jsonwebtoken";
|
|
3
|
-
|
|
4
|
-
const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production";
|
|
5
|
-
|
|
6
|
-
const PUBLIC_PATHS = [
|
|
7
|
-
"/api/auth/login",
|
|
8
|
-
"/api/auth/logout",
|
|
9
|
-
"/api/auth/register",
|
|
10
|
-
"/api/auth/me",
|
|
11
|
-
"/api/auth/users",
|
|
12
|
-
"/api/auth/refresh",
|
|
13
|
-
"/api/users",
|
|
14
|
-
"/api/health",
|
|
15
|
-
"/api/search",
|
|
16
|
-
"/api/upload",
|
|
17
|
-
"/api/media",
|
|
18
|
-
"/login",
|
|
19
|
-
"/register",
|
|
20
|
-
"/favicon.svg",
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const PUBLIC_PREFIXES = [
|
|
24
|
-
"/api/collections/",
|
|
25
|
-
"/api/auth/",
|
|
26
|
-
"/api/globals/",
|
|
27
|
-
"/api/media/",
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const isApiRequest = (pathname: string): boolean => {
|
|
31
|
-
return pathname.startsWith("/api/");
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const redirectToLogin = (): Response => {
|
|
35
|
-
return new Response(null, {
|
|
36
|
-
status: 302,
|
|
37
|
-
headers: {
|
|
38
|
-
Location: "/login",
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const onRequest: MiddlewareHandler = async (
|
|
44
|
-
{ request, url, locals },
|
|
45
|
-
next,
|
|
46
|
-
) => {
|
|
47
|
-
const pathname = new URL(url).pathname;
|
|
48
|
-
|
|
49
|
-
// Helper to extract token from cookie or header
|
|
50
|
-
const getToken = (): string | null => {
|
|
51
|
-
// Check Authorization header first
|
|
52
|
-
const authHeader = request.headers.get("authorization");
|
|
53
|
-
if (authHeader?.startsWith("Bearer ")) {
|
|
54
|
-
return authHeader.slice(7);
|
|
55
|
-
}
|
|
56
|
-
// Check cookie
|
|
57
|
-
const cookies = request.headers.get("cookie") || "";
|
|
58
|
-
const match = cookies.match(/auth_token=([^;]+)/);
|
|
59
|
-
return match ? match[1] : null;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const token = getToken();
|
|
63
|
-
|
|
64
|
-
// Set user in locals if token is valid
|
|
65
|
-
if (token) {
|
|
66
|
-
try {
|
|
67
|
-
const payload = jwt.verify(token, JWT_SECRET) as jwt.JwtPayload;
|
|
68
|
-
locals.user = {
|
|
69
|
-
id: payload.sub || "",
|
|
70
|
-
email: (payload as any).email || "",
|
|
71
|
-
role: (payload as any).role || "guest",
|
|
72
|
-
tenantId: (payload as any).tenantId,
|
|
73
|
-
};
|
|
74
|
-
} catch {
|
|
75
|
-
// Token invalid, leave user undefined
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Handle root path - redirect to admin for authenticated users
|
|
80
|
-
if (pathname === "/") {
|
|
81
|
-
if (!token) {
|
|
82
|
-
return new Response(null, {
|
|
83
|
-
status: 302,
|
|
84
|
-
headers: {
|
|
85
|
-
Location: "/login",
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Token exists - redirect to admin dashboard
|
|
91
|
-
try {
|
|
92
|
-
jwt.verify(token, JWT_SECRET);
|
|
93
|
-
return new Response(null, {
|
|
94
|
-
status: 302,
|
|
95
|
-
headers: {
|
|
96
|
-
Location: "/admin",
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
} catch {
|
|
100
|
-
return new Response(null, {
|
|
101
|
-
status: 302,
|
|
102
|
-
headers: {
|
|
103
|
-
Location: "/login",
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Handle /admin path - main dashboard
|
|
110
|
-
if (pathname === "/admin") {
|
|
111
|
-
if (!token) {
|
|
112
|
-
return new Response(null, {
|
|
113
|
-
status: 302,
|
|
114
|
-
headers: {
|
|
115
|
-
Location: "/login",
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
jwt.verify(token, JWT_SECRET);
|
|
122
|
-
return next();
|
|
123
|
-
} catch {
|
|
124
|
-
return new Response(null, {
|
|
125
|
-
status: 302,
|
|
126
|
-
headers: {
|
|
127
|
-
Location: "/login",
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (PUBLIC_PATHS.includes(pathname)) {
|
|
134
|
-
return next();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (const prefix of PUBLIC_PREFIXES) {
|
|
138
|
-
if (pathname.startsWith(prefix)) {
|
|
139
|
-
return next();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!token) {
|
|
144
|
-
if (!isApiRequest(pathname)) {
|
|
145
|
-
return redirectToLogin();
|
|
146
|
-
}
|
|
147
|
-
return new Response(
|
|
148
|
-
JSON.stringify({ error: "Authentication required. Please log in." }),
|
|
149
|
-
{
|
|
150
|
-
status: 401,
|
|
151
|
-
headers: { "Content-Type": "application/json" },
|
|
152
|
-
},
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
jwt.verify(token, JWT_SECRET);
|
|
158
|
-
return next();
|
|
159
|
-
} catch (err) {
|
|
160
|
-
const isExpired = err instanceof jwt.TokenExpiredError;
|
|
161
|
-
if (!isApiRequest(pathname)) {
|
|
162
|
-
return redirectToLogin();
|
|
163
|
-
}
|
|
164
|
-
return new Response(
|
|
165
|
-
JSON.stringify({
|
|
166
|
-
error: isExpired
|
|
167
|
-
? "Token expired. Please refresh your session or log in again."
|
|
168
|
-
: "Invalid token. Please log in again.",
|
|
169
|
-
code: isExpired ? "TOKEN_EXPIRED" : "TOKEN_INVALID",
|
|
170
|
-
}),
|
|
171
|
-
{
|
|
172
|
-
status: 401,
|
|
173
|
-
headers: { "Content-Type": "application/json" },
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
};
|