@kyro-cms/admin 0.3.2 → 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.
Files changed (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,206 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { getDatabaseConfig } from "../../lib/db";
3
-
4
- interface RawStorageSettings {
5
- provider?: string;
6
- local?: {
7
- uploadDir?: string;
8
- baseUrl?: string;
9
- };
10
- aws?: Record<string, any>;
11
- r2?: Record<string, any>;
12
- gcs?: Record<string, any>;
13
- digitalocean?: Record<string, any>;
14
- backblaze?: Record<string, any>;
15
- wasabi?: Record<string, any>;
16
- bunny?: Record<string, any>;
17
- cloudinary?: Record<string, any>;
18
- imgix?: Record<string, any>;
19
- ftp?: Record<string, any>;
20
- limits?: Record<string, any>;
21
- [key: string]: any;
22
- }
23
-
24
- function validateProviderConfig(
25
- provider: string,
26
- config: RawStorageSettings,
27
- ): { valid: boolean; missing: string[] } {
28
- const missing: string[] = [];
29
-
30
- switch (provider) {
31
- case "local": {
32
- const local = config.local || {};
33
- if (!local.uploadDir || local.uploadDir.trim() === "") {
34
- missing.push("Upload Directory");
35
- }
36
- if (
37
- !local.baseUrl ||
38
- local.baseUrl.trim() === "" ||
39
- local.baseUrl === "/"
40
- ) {
41
- missing.push("Base URL");
42
- }
43
- break;
44
- }
45
-
46
- case "aws": {
47
- const aws = config.aws || {};
48
- if (!aws.bucket?.trim()) missing.push("Bucket Name");
49
- if (!aws.accessKeyId?.trim()) missing.push("Access Key ID");
50
- if (!aws.secretAccessKey?.trim()) missing.push("Secret Access Key");
51
- break;
52
- }
53
-
54
- case "r2": {
55
- const r2 = config.r2 || {};
56
- if (!r2.bucket?.trim()) missing.push("Bucket Name");
57
- if (!r2.accountId?.trim()) missing.push("Account ID");
58
- if (!r2.accessKeyId?.trim()) missing.push("Access Key ID");
59
- if (!r2.secretAccessKey?.trim()) missing.push("Secret Access Key");
60
- break;
61
- }
62
-
63
- case "gcs": {
64
- const gcs = config.gcs || {};
65
- if (!gcs.bucket?.trim()) missing.push("Bucket Name");
66
- if (!gcs.projectId?.trim()) missing.push("Project ID");
67
- if (!gcs.clientEmail?.trim()) missing.push("Client Email");
68
- if (!gcs.privateKey?.trim()) missing.push("Private Key");
69
- break;
70
- }
71
-
72
- case "digitalocean": {
73
- const doConfig = config.digitalocean || {};
74
- if (!doConfig.bucket?.trim()) missing.push("Space Name");
75
- if (!doConfig.accessKeyId?.trim()) missing.push("Access Key ID");
76
- if (!doConfig.secretAccessKey?.trim()) missing.push("Secret Access Key");
77
- break;
78
- }
79
-
80
- case "backblaze": {
81
- const b2 = config.backblaze || {};
82
- if (!b2.bucket?.trim()) missing.push("Bucket Name");
83
- if (!b2.accountId?.trim()) missing.push("Account ID");
84
- if (!b2.applicationKeyId?.trim()) missing.push("Application Key ID");
85
- if (!b2.applicationKey?.trim()) missing.push("Application Key");
86
- break;
87
- }
88
-
89
- case "wasabi": {
90
- const wasabi = config.wasabi || {};
91
- if (!wasabi.bucket?.trim()) missing.push("Bucket Name");
92
- if (!wasabi.accessKeyId?.trim()) missing.push("Access Key ID");
93
- if (!wasabi.secretAccessKey?.trim()) missing.push("Secret Access Key");
94
- break;
95
- }
96
-
97
- case "bunny": {
98
- const bunny = config.bunny || {};
99
- if (!bunny.storageZone?.trim()) missing.push("Storage Zone");
100
- if (!bunny.apiKey?.trim()) missing.push("API Key");
101
- break;
102
- }
103
-
104
- case "cloudinary": {
105
- const cloudinary = config.cloudinary || {};
106
- if (!cloudinary.cloudName?.trim()) missing.push("Cloud Name");
107
- if (!cloudinary.apiKey?.trim()) missing.push("API Key");
108
- if (!cloudinary.apiSecret?.trim()) missing.push("API Secret");
109
- break;
110
- }
111
-
112
- case "imgix": {
113
- const imgix = config.imgix || {};
114
- if (!imgix.domain?.trim()) missing.push("Imgix Domain");
115
- break;
116
- }
117
-
118
- case "ftp": {
119
- const ftp = config.ftp || {};
120
- if (!ftp.host?.trim()) missing.push("Host");
121
- if (!ftp.user?.trim()) missing.push("Username");
122
- if (!ftp.password?.trim()) missing.push("Password");
123
- if (!ftp.baseUrl?.trim()) missing.push("Base URL");
124
- break;
125
- }
126
-
127
- default:
128
- missing.push("Unknown provider");
129
- }
130
-
131
- return { valid: missing.length === 0, missing };
132
- }
133
-
134
- export const GET: APIRoute = async () => {
135
- const dbConfig = getDatabaseConfig();
136
-
137
- try {
138
- if (dbConfig.type === "sqlite") {
139
- const Database = (await import("better-sqlite3")).default;
140
- const db = new Database(dbConfig.contentDbPath || "./data/content.db");
141
-
142
- try {
143
- const row = db
144
- .prepare("SELECT data FROM globals WHERE slug = ?")
145
- .get("storage-settings") as { data: string } | undefined;
146
-
147
- if (!row) {
148
- return new Response(
149
- JSON.stringify({
150
- configured: false,
151
- reason: "no-settings",
152
- provider: null,
153
- missingFields: [],
154
- }),
155
- { status: 200, headers: { "Content-Type": "application/json" } },
156
- );
157
- }
158
-
159
- const data: RawStorageSettings =
160
- typeof row.data === "string" ? JSON.parse(row.data) : row.data;
161
- const provider = data.provider || "local";
162
-
163
- // Validate based on provider
164
- const validation = validateProviderConfig(provider, data);
165
-
166
- if (!validation.valid) {
167
- return new Response(
168
- JSON.stringify({
169
- configured: false,
170
- reason: "incomplete-config",
171
- provider,
172
- missingFields: validation.missing,
173
- }),
174
- { status: 200, headers: { "Content-Type": "application/json" } },
175
- );
176
- }
177
-
178
- return new Response(
179
- JSON.stringify({
180
- configured: true,
181
- reason: "configured",
182
- provider,
183
- missingFields: [],
184
- }),
185
- { status: 200, headers: { "Content-Type": "application/json" } },
186
- );
187
- } finally {
188
- db.close();
189
- }
190
- }
191
-
192
- return new Response(
193
- JSON.stringify({ configured: false, reason: "unsupported-db" }),
194
- { status: 200, headers: { "Content-Type": "application/json" } },
195
- );
196
- } catch (error) {
197
- return new Response(
198
- JSON.stringify({
199
- configured: false,
200
- reason: "error",
201
- details: String(error),
202
- }),
203
- { status: 500, headers: { "Content-Type": "application/json" } },
204
- );
205
- }
206
- };
@@ -1,334 +0,0 @@
1
- import type { APIRoute } from "astro";
2
- import { MediaService, type Dialect } from "@kyro-cms/core";
3
- import { getDatabaseConfig, runMigrations } from "../../lib/db";
4
-
5
- const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
6
-
7
- const ALLOWED_TYPES = [
8
- "image/jpeg",
9
- "image/png",
10
- "image/gif",
11
- "image/webp",
12
- "image/avif",
13
- "image/svg+xml",
14
- "video/mp4",
15
- "video/webm",
16
- "video/ogg",
17
- "application/pdf",
18
- "application/zip",
19
- "text/plain",
20
- "application/octet-stream",
21
- ];
22
-
23
- async function ensureMediaTable(db: any) {
24
- try {
25
- db.exec(`
26
- CREATE TABLE IF NOT EXISTS media (
27
- id TEXT PRIMARY KEY,
28
- filename TEXT NOT NULL UNIQUE,
29
- title TEXT,
30
- original_name TEXT NOT NULL,
31
- mime_type TEXT NOT NULL,
32
- file_size INTEGER NOT NULL,
33
- width INTEGER,
34
- height INTEGER,
35
- url TEXT NOT NULL UNIQUE,
36
- thumbnail_url TEXT,
37
- folder TEXT,
38
- provider TEXT NOT NULL DEFAULT 'local',
39
- alt TEXT,
40
- caption TEXT,
41
- metadata TEXT,
42
- created_at TEXT DEFAULT (datetime('now')),
43
- updated_at TEXT DEFAULT (datetime('now'))
44
- )
45
- `);
46
- } catch (e: any) {
47
- console.warn("[upload] Media table error:", e.message);
48
- }
49
- }
50
-
51
- export const POST: APIRoute = async ({ request }) => {
52
- const dbConfig = getDatabaseConfig();
53
-
54
- let db: any = null;
55
- let dialect: Dialect = "sqlite";
56
- let genId = () => crypto.randomUUID();
57
-
58
- // Connect to admin's database (same as admin UI uses)
59
- try {
60
- if (dbConfig.type === "sqlite") {
61
- const Database = (await import("better-sqlite3")).default;
62
- db = new Database(dbConfig.contentDbPath || "./data/content.db");
63
- dialect = "sqlite";
64
- } else if (dbConfig.type === "postgres") {
65
- const { Pool } = await import("pg");
66
- db = new Pool({
67
- connectionString:
68
- dbConfig.connectionString ||
69
- `postgresql://${dbConfig.username}:${dbConfig.password}@${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`,
70
- });
71
- dialect = "postgres";
72
- } else if (dbConfig.type === "mysql") {
73
- const mysql = await import("mysql2/promise");
74
- db = await mysql.createPool({
75
- uri:
76
- dbConfig.connectionString ||
77
- `mysql://${dbConfig.username}:${dbConfig.password}@${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`,
78
- });
79
- dialect = "mysql";
80
- }
81
- } catch (e: any) {
82
- return new Response(
83
- JSON.stringify({ error: "Database connection failed: " + e.message }),
84
- { status: 500, headers: { "Content-Type": "application/json" } },
85
- );
86
- }
87
-
88
- // Get storage config from globals table
89
- let storageConfig: any = undefined;
90
-
91
- try {
92
- if (dbConfig.type === "sqlite") {
93
- const stmt = db.prepare("SELECT * FROM globals WHERE slug = ?");
94
- const row = stmt.get("storage-settings") as any;
95
- if (row) {
96
- storageConfig =
97
- typeof row.data === "string" ? JSON.parse(row.data) : row.data;
98
- if (storageConfig.provider && !storageConfig.type) {
99
- storageConfig.type = storageConfig.provider;
100
- }
101
- console.log(
102
- "[upload] Storage config loaded:",
103
- JSON.stringify(
104
- {
105
- provider: storageConfig.provider,
106
- type: storageConfig.type,
107
- r2: storageConfig.r2
108
- ? {
109
- bucket: storageConfig.r2.bucket,
110
- accountId: storageConfig.r2.accountId,
111
- publicDevUrl: storageConfig.r2.publicDevUrl,
112
- prefix: storageConfig.r2.prefix,
113
- }
114
- : undefined,
115
- },
116
- null,
117
- 2,
118
- ),
119
- );
120
- }
121
- } else if (dbConfig.type === "postgres" || dbConfig.type === "mysql") {
122
- const result = await db.query("SELECT * FROM globals WHERE slug = ?", [
123
- "storage-settings",
124
- ]);
125
- const rows = result.rows || result[0] || [];
126
- if (rows.length > 0) {
127
- storageConfig = rows[0].data;
128
- if (storageConfig.provider && !storageConfig.type) {
129
- storageConfig.type = storageConfig.provider;
130
- }
131
- }
132
- }
133
- } catch (e: any) {
134
- console.warn("[upload] Error reading storage config:", e.message);
135
- }
136
-
137
- // Fallback to environment or default
138
- if (!storageConfig && process.env.STORAGE_TYPE) {
139
- storageConfig = {
140
- type: process.env.STORAGE_TYPE,
141
- [process.env.STORAGE_TYPE]: {
142
- bucket: process.env.STORAGE_BUCKET,
143
- region: process.env.STORAGE_REGION,
144
- accessKeyId: process.env.STORAGE_ACCESS_KEY_ID,
145
- secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY,
146
- },
147
- };
148
- }
149
-
150
- if (!storageConfig) {
151
- storageConfig = { type: "local" };
152
- }
153
-
154
- // Recreate media table
155
- if (dbConfig.type === "sqlite") {
156
- await ensureMediaTable(db);
157
- } else {
158
- try {
159
- await runMigrations();
160
- } catch (e: any) {
161
- console.warn("[upload] runMigrations:", e.message);
162
- }
163
- }
164
-
165
- // Initialize MediaService
166
- let mediaService;
167
- try {
168
- console.log(
169
- "[upload] About to init MediaService with config:",
170
- JSON.stringify(
171
- {
172
- type: storageConfig.type,
173
- r2: storageConfig.r2
174
- ? {
175
- bucket: storageConfig.r2.bucket,
176
- accountId: storageConfig.r2.accountId,
177
- accessKeyId: storageConfig.r2.accessKeyId ? "SET" : "UNDEFINED",
178
- secretAccessKey: storageConfig.r2.secretAccessKey
179
- ? "SET"
180
- : "UNDEFINED",
181
- publicDevUrl: storageConfig.r2.publicDevUrl,
182
- prefix: storageConfig.r2.prefix,
183
- }
184
- : undefined,
185
- },
186
- null,
187
- 2,
188
- ),
189
- );
190
-
191
- mediaService = await MediaService.init(db, {
192
- dialect,
193
- genId,
194
- storageConfig,
195
- });
196
- } catch (e: any) {
197
- return new Response(
198
- JSON.stringify({ error: "Failed to init media service: " + e.message }),
199
- { status: 500, headers: { "Content-Type": "application/json" } },
200
- );
201
- }
202
-
203
- // Handle file upload
204
- try {
205
- const contentType = request.headers.get("Content-Type") || "";
206
-
207
- if (contentType.includes("application/json")) {
208
- const body = await request.json();
209
- const { url, folder } = body;
210
-
211
- if (url) {
212
- try {
213
- const isExternalUrl =
214
- url.startsWith("http://") || url.startsWith("https://");
215
-
216
- if (isExternalUrl) {
217
- const response = await fetch(url);
218
- if (!response.ok) {
219
- return new Response(
220
- JSON.stringify({
221
- error: "Failed to fetch URL: " + response.statusText,
222
- }),
223
- {
224
- status: 400,
225
- headers: { "Content-Type": "application/json" },
226
- },
227
- );
228
- }
229
- const blob = await response.blob();
230
- const contentDisposition = response.headers.get(
231
- "Content-Disposition",
232
- );
233
- const filenameMatch = contentDisposition?.match(
234
- /filename[^;]*=([^";\s]+)/,
235
- );
236
- const filename =
237
- filenameMatch?.[1] || url.split("/").pop() || "url-upload";
238
-
239
- const file = new File([blob], filename, {
240
- type: blob.type || "image/*",
241
- });
242
- const media = await mediaService.upload(file, folder || "");
243
- return new Response(JSON.stringify(media), {
244
- status: 200,
245
- headers: { "Content-Type": "application/json" },
246
- });
247
- }
248
-
249
- const fileMatches = url.match(/\/uploads\/(.+)/);
250
- if (fileMatches) {
251
- const fullPath = fileMatches[1];
252
- const fullUrl =
253
- new URL(url, request.url).origin + "/uploads/" + fullPath;
254
- const { default: fs } = await import("fs/promises");
255
- const path = await import("path");
256
- const filePath = path.join(
257
- process.cwd(),
258
- "public",
259
- "uploads",
260
- fullPath,
261
- );
262
- const data = await fs.readFile(filePath);
263
- const file = new File([data], fullPath);
264
- const media = await mediaService.upload(file, folder || "");
265
- return new Response(JSON.stringify(media), {
266
- status: 200,
267
- headers: { "Content-Type": "application/json" },
268
- });
269
- }
270
- } catch (fetchErr: any) {
271
- return new Response(
272
- JSON.stringify({ error: "Failed to add URL: " + fetchErr.message }),
273
- {
274
- status: 400,
275
- headers: { "Content-Type": "application/json" },
276
- },
277
- );
278
- }
279
- }
280
-
281
- return new Response(
282
- JSON.stringify({ error: "No file or URL provided" }),
283
- {
284
- status: 400,
285
- headers: { "Content-Type": "application/json" },
286
- },
287
- );
288
- }
289
-
290
- const formData = await request.formData();
291
- const file = formData.get("file") as File | null;
292
- const folder = (formData.get("folder") as string) || "";
293
-
294
- if (!file) {
295
- return new Response(JSON.stringify({ error: "No file provided" }), {
296
- status: 400,
297
- headers: { "Content-Type": "application/json" },
298
- });
299
- }
300
-
301
- if (!ALLOWED_TYPES.includes(file.type)) {
302
- return new Response(
303
- JSON.stringify({ error: `Invalid file type: ${file.type}` }),
304
- { status: 400, headers: { "Content-Type": "application/json" } },
305
- );
306
- }
307
-
308
- if (file.size > MAX_FILE_SIZE) {
309
- return new Response(
310
- JSON.stringify({
311
- error: `File too large. Max size: ${MAX_FILE_SIZE / 1024 / 1024}MB`,
312
- }),
313
- { status: 400, headers: { "Content-Type": "application/json" } },
314
- );
315
- }
316
-
317
- const media = await mediaService.upload(file, folder);
318
-
319
- return new Response(JSON.stringify(media), {
320
- status: 200,
321
- headers: { "Content-Type": "application/json" },
322
- });
323
- } catch (error: any) {
324
- console.error("Upload error:", error);
325
- return new Response(
326
- JSON.stringify({ error: error.message || "Upload failed" }),
327
- { status: 500, headers: { "Content-Type": "application/json" } },
328
- );
329
- } finally {
330
- if (dbConfig.type === "sqlite" && db) {
331
- db.close();
332
- }
333
- }
334
- };
@@ -1,71 +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 webhooks = await dataStore.find("_webhooks", { limit: 100, page: 1 });
7
-
8
- const docs = webhooks.docs.map((w: any) => ({
9
- id: w.id,
10
- name: w.name,
11
- url: w.url,
12
- events: w.events || [],
13
- secret: w.secret,
14
- status: w.status || "active",
15
- createdAt: w.createdAt,
16
- lastTriggered: w.lastTriggered,
17
- }));
18
-
19
- return new Response(JSON.stringify(docs), {
20
- status: 200,
21
- headers: { "Content-Type": "application/json" },
22
- });
23
- } catch (error: any) {
24
- return new Response(JSON.stringify({ error: error.message }), {
25
- status: 500,
26
- headers: { "Content-Type": "application/json" },
27
- });
28
- }
29
- };
30
-
31
- export const POST: APIRoute = async ({ request }) => {
32
- try {
33
- const body = await request.json();
34
- const { name, url, events, secret } = body;
35
-
36
- if (!name || !url) {
37
- return new Response(
38
- JSON.stringify({ error: "Name and URL are required" }),
39
- {
40
- status: 400,
41
- headers: { "Content-Type": "application/json" },
42
- },
43
- );
44
- }
45
-
46
- const doc = await dataStore.create("_webhooks", {
47
- name,
48
- url,
49
- events: events || [],
50
- secret: secret || "",
51
- status: "active",
52
- createdAt: new Date().toISOString(),
53
- });
54
-
55
- return new Response(
56
- JSON.stringify({
57
- id: doc.id,
58
- name: doc.name,
59
- }),
60
- {
61
- status: 201,
62
- headers: { "Content-Type": "application/json" },
63
- },
64
- );
65
- } catch (error: any) {
66
- return new Response(JSON.stringify({ error: error.message }), {
67
- status: 500,
68
- headers: { "Content-Type": "application/json" },
69
- });
70
- }
71
- };
@@ -1,82 +0,0 @@
1
- ---
2
- import AuthLayout from '../layouts/AuthLayout.astro';
3
- ---
4
-
5
- <AuthLayout title="Sign In">
6
- <div class="surface-tile p-8 w-full" style="max-width: 420px;">
7
- <div class="text-center mb-8">
8
- <h1 class="text-2xl font-black tracking-tight text-[var(--kyro-text-primary)]">Welcome back</h1>
9
- <p class="text-sm text-[var(--kyro-text-secondary)] mt-2">Sign in to your account</p>
10
- </div>
11
-
12
- <form id="login-form" class="space-y-5">
13
- <div>
14
- <label for="email" class="block text-sm font-bold text-[var(--kyro-text-primary)] mb-2 text-left">Email</label>
15
- <input type="email" id="email" name="email" required
16
- class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
17
- placeholder="admin@example.com" />
18
- </div>
19
-
20
- <div>
21
- <label for="password" class="block text-sm font-bold text-[var(--kyro-text-primary)] mb-2">Password</label>
22
- <input type="password" id="password" name="password" required
23
- class="w-full px-4 py-3 border border-[var(--kyro-border)] bg-[var(--kyro-input-bg)] rounded-xl text-sm font-medium focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-[var(--kyro-sidebar-active)] text-[var(--kyro-text-primary)]"
24
- placeholder="••••••••" />
25
- </div>
26
-
27
- <div id="form-message" class="hidden p-3 rounded-xl text-sm font-bold"></div>
28
-
29
- <button type="submit" class="w-full py-3 bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] rounded-xl text-sm font-bold hover:opacity-90 transition-colors shadow-lg">
30
- Sign In
31
- </button>
32
- </form>
33
-
34
- <p class="text-center text-sm text-[var(--kyro-text-secondary)] mt-6">
35
- Don't have an account? <a href="/register" class="font-bold text-[var(--kyro-text-primary)] hover:underline">Register</a>
36
- </p>
37
- </div>
38
-
39
- <script is:inline>
40
- document.getElementById('login-form')?.addEventListener('submit', async (e) => {
41
- e.preventDefault();
42
- const form = e.target;
43
- const message = document.getElementById('form-message');
44
- const button = form.querySelector('button[type="submit"]');
45
-
46
- const email = form.email.value;
47
- const password = form.password.value;
48
-
49
- button.disabled = true;
50
- button.textContent = 'Signing in...';
51
-
52
- try {
53
- const res = await fetch('/api/auth/login', {
54
- method: 'POST',
55
- headers: { 'Content-Type': 'application/json' },
56
- body: JSON.stringify({ email, password })
57
- });
58
-
59
- const data = await res.json();
60
-
61
- if (res.ok && data.success) {
62
- // Set cookie for server-side auth
63
- document.cookie = `auth_token=${data.token}; path=/; max-age=${60*60*24}; samesite=strict`;
64
- localStorage.setItem('user', JSON.stringify(data.user));
65
- message.textContent = 'Success! Redirecting...';
66
- message.className = 'block p-3 rounded-xl text-sm font-bold bg-green-50 text-green-600';
67
- setTimeout(() => { window.location.href = '/admin'; }, 500);
68
- } else {
69
- message.textContent = data.error || 'Login failed';
70
- message.className = 'block p-3 rounded-xl text-sm font-bold bg-red-50 text-red-600';
71
- button.disabled = false;
72
- button.textContent = 'Sign In';
73
- }
74
- } catch (err) {
75
- message.textContent = 'Connection error';
76
- message.className = 'block p-3 rounded-xl text-sm font-bold bg-red-50 text-red-600';
77
- button.disabled = false;
78
- button.textContent = 'Sign In';
79
- }
80
- });
81
- </script>
82
- </AuthLayout>