@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,449 +0,0 @@
1
- import type { CollectionConfig } from "@kyro-cms/core";
2
- import type { DatabaseAdapter, DatabaseConfig, DatabaseType } from "./adapter";
3
- import type { AuthAdapter } from "@kyro-cms/core";
4
-
5
- let contentAdapter: DatabaseAdapter | null = null;
6
- let authAdapterInstance: AuthAdapter | null = null;
7
- let currentConfig: DatabaseConfig | null = null;
8
-
9
- export interface DatabaseConfig {
10
- type: DatabaseType;
11
- connectionString?: string;
12
- host?: string;
13
- port?: number;
14
- database?: string;
15
- username?: string;
16
- password?: string;
17
- poolMin?: number;
18
- poolMax?: number;
19
- contentDbPath?: string;
20
- authDbPath?: string;
21
- }
22
-
23
- export function getDatabaseConfig(): DatabaseConfig {
24
- const connectionString =
25
- process.env.DB_CONNECTION_STRING || process.env.DATABASE_URL;
26
-
27
- let type: DatabaseType = "sqlite";
28
- if (connectionString) {
29
- if (connectionString.startsWith("postgresql://")) type = "postgres";
30
- else if (connectionString.startsWith("mysql://")) type = "mysql";
31
- else if (connectionString.startsWith("mongodb://")) type = "mongodb";
32
- } else if (process.env.DB_TYPE) {
33
- type = process.env.DB_TYPE as DatabaseType;
34
- }
35
-
36
- return {
37
- type,
38
- connectionString,
39
- host: process.env.DB_HOST || "localhost",
40
- port: parseInt(process.env.DB_PORT || "5432"),
41
- database:
42
- process.env.DB_NAME || connectionString?.split("/").pop() || "kyro",
43
- username: process.env.DB_USER,
44
- password: process.env.DB_PASSWORD,
45
- poolMin: parseInt(process.env.DB_POOL_MIN || "5"),
46
- poolMax: parseInt(process.env.DB_POOL_MAX || "20"),
47
- contentDbPath: process.env.CONTENT_DB_PATH || "./data/content.db",
48
- authDbPath: process.env.AUTH_DB_PATH || "./data/auth.db",
49
- };
50
- }
51
-
52
- export function getAuthConfig(): DatabaseConfig {
53
- const type =
54
- (process.env.AUTH_DB_TYPE as DatabaseType) || getDatabaseConfig().type;
55
-
56
- return {
57
- type,
58
- connectionString:
59
- process.env.AUTH_DB_CONNECTION_STRING || process.env.AUTH_DATABASE_URL,
60
- host: process.env.AUTH_DB_HOST || getDatabaseConfig().host,
61
- port: parseInt(
62
- process.env.AUTH_DB_PORT || String(getDatabaseConfig().port),
63
- ),
64
- database: process.env.AUTH_DB_NAME || getDatabaseConfig().database,
65
- username: process.env.AUTH_DB_USER || getDatabaseConfig().username,
66
- password: process.env.AUTH_DB_PASSWORD || getDatabaseConfig().password,
67
- authDbPath: process.env.AUTH_DB_PATH || "./data/auth.db",
68
- };
69
- }
70
-
71
- export async function initializeDatabase(
72
- collections: Record<string, CollectionConfig> = {},
73
- ): Promise<DatabaseAdapter> {
74
- const config = getDatabaseConfig();
75
-
76
- if (contentAdapter && currentConfig?.type === config.type) {
77
- contentAdapter.initialize(collections);
78
- return contentAdapter;
79
- }
80
-
81
- currentConfig = config;
82
-
83
- switch (config.type) {
84
- case "sqlite": {
85
- const { getSQLiteContentAdapter } =
86
- await import("./drizzle-sqlite-adapter");
87
- contentAdapter = getSQLiteContentAdapter(config);
88
- break;
89
- }
90
- case "postgres": {
91
- const { PostgresAdapter } = await import("./drizzle-postgres-adapter");
92
- contentAdapter = new PostgresAdapter(config);
93
- break;
94
- }
95
- case "mysql": {
96
- const { MySQLAdapter } = await import("./drizzle-mysql-adapter");
97
- contentAdapter = new MySQLAdapter(config);
98
- break;
99
- }
100
- case "mongodb": {
101
- const { MongoDBAdapter } = await import("./mongodb-adapter");
102
- contentAdapter = new MongoDBAdapter(config);
103
- break;
104
- }
105
- default:
106
- throw new Error(`Unsupported database type: ${config.type}`);
107
- }
108
-
109
- contentAdapter.initialize(collections);
110
- return contentAdapter;
111
- }
112
-
113
- export function getDatabaseAdapter(): DatabaseAdapter {
114
- if (!contentAdapter) {
115
- throw new Error(
116
- "Database not initialized. Call initializeDatabase() first.",
117
- );
118
- }
119
- return contentAdapter;
120
- }
121
-
122
- export function isDatabaseInitialized(): boolean {
123
- return contentAdapter !== null;
124
- }
125
-
126
- export async function getAuthAdapter(): Promise<AuthAdapter> {
127
- if (authAdapterInstance) {
128
- return authAdapterInstance;
129
- }
130
-
131
- const authDbType =
132
- (process.env.AUTH_DB_TYPE as DatabaseType) || getDatabaseConfig().type;
133
- const config = getAuthConfig();
134
-
135
- switch (authDbType) {
136
- case "sqlite": {
137
- const { getSQLiteAuthAdapter } =
138
- await import("./drizzle-sqlite-auth-adapter");
139
- authAdapterInstance = getSQLiteAuthAdapter();
140
- break;
141
- }
142
- case "postgres": {
143
- const { PostgresAuthAdapter } =
144
- await import("./drizzle-postgres-auth-adapter");
145
- authAdapterInstance = new PostgresAuthAdapter({
146
- connectionString:
147
- config.connectionString ||
148
- `postgresql://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`,
149
- });
150
- break;
151
- }
152
- case "mysql": {
153
- const { MySQLAuthAdapter } = await import("./drizzle-mysql-auth-adapter");
154
- authAdapterInstance = new MySQLAuthAdapter({
155
- connectionString:
156
- config.connectionString ||
157
- `mysql://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`,
158
- });
159
- break;
160
- }
161
- case "mongodb": {
162
- const { MongoDBAuthAdapter } = await import("./mongodb-auth-adapter");
163
- authAdapterInstance = new MongoDBAuthAdapter({
164
- connectionString:
165
- config.connectionString ||
166
- `mongodb://${config.username}:${config.password}@${config.host}:${config.port}`,
167
- database: config.database || "kyro",
168
- });
169
- break;
170
- }
171
- default: {
172
- const { getSQLiteAuthAdapter: fallback } =
173
- await import("./drizzle-sqlite-auth-adapter");
174
- authAdapterInstance = fallback();
175
- }
176
- }
177
-
178
- return authAdapterInstance;
179
- }
180
-
181
- export function getDatabaseType(): DatabaseType {
182
- return getDatabaseConfig().type;
183
- }
184
-
185
- export function getAuthDatabaseType(): DatabaseType {
186
- return (process.env.AUTH_DB_TYPE as DatabaseType) || getDatabaseConfig().type;
187
- }
188
-
189
- let migrationsRun = false;
190
-
191
- export async function runMigrations(): Promise<void> {
192
- if (migrationsRun) return;
193
-
194
- const config = getDatabaseConfig();
195
- const dbType = config.type;
196
-
197
- console.log(`[db] Running migrations for ${dbType}...`);
198
-
199
- try {
200
- if (dbType === "postgres") {
201
- const { default: pg } = await import("pg");
202
- const pool = new pg.Pool({
203
- connectionString:
204
- config.connectionString ||
205
- `postgresql://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`,
206
- });
207
-
208
- const client = await pool.connect();
209
-
210
- await client.query(`
211
- CREATE TABLE IF NOT EXISTS settings (
212
- key VARCHAR(255) PRIMARY KEY,
213
- value TEXT NOT NULL,
214
- description TEXT,
215
- updated_at TIMESTAMP DEFAULT NOW()
216
- )
217
- `);
218
-
219
- await client.query(`
220
- CREATE TABLE IF NOT EXISTS media (
221
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
222
- filename VARCHAR(255) NOT NULL UNIQUE,
223
- title VARCHAR(255),
224
- original_name VARCHAR(255) NOT NULL,
225
- mime_type VARCHAR(100) NOT NULL,
226
- file_size INTEGER NOT NULL,
227
- width INTEGER,
228
- height INTEGER,
229
- url TEXT NOT NULL UNIQUE,
230
- thumbnail_url TEXT,
231
- folder VARCHAR(255),
232
- provider VARCHAR(50) NOT NULL,
233
- alt TEXT,
234
- caption TEXT,
235
- metadata JSONB,
236
- created_at TIMESTAMP DEFAULT NOW() NOT NULL,
237
- updated_at TIMESTAMP DEFAULT NOW() NOT NULL
238
- )
239
- `);
240
-
241
- await client.query(
242
- `CREATE INDEX IF NOT EXISTS media_folder_idx ON media(folder)`,
243
- );
244
- await client.query(
245
- `CREATE INDEX IF NOT EXISTS media_provider_idx ON media(provider)`,
246
- );
247
- await client.query(
248
- `CREATE INDEX IF NOT EXISTS media_filename_idx ON media(filename)`,
249
- );
250
-
251
- await client.query(`
252
- CREATE TABLE IF NOT EXISTS media_folders (
253
- path VARCHAR(500) PRIMARY KEY,
254
- name VARCHAR(255) NOT NULL,
255
- parent_path VARCHAR(500),
256
- created_at TIMESTAMP DEFAULT NOW()
257
- )
258
- `);
259
-
260
- client.release();
261
- await pool.end();
262
-
263
- migrationsRun = true;
264
- console.log(`[db] Migrations complete`);
265
- } else if (dbType === "sqlite") {
266
- const Database = (await import("better-sqlite3")).default;
267
- const db = new Database(config.contentDbPath || "./data/content.db");
268
-
269
- db.exec(`
270
- CREATE TABLE IF NOT EXISTS settings (
271
- key TEXT PRIMARY KEY,
272
- value TEXT NOT NULL,
273
- description TEXT,
274
- updated_at TEXT DEFAULT (datetime('now'))
275
- )
276
- `);
277
-
278
- try {
279
- db.exec(`
280
- CREATE TABLE IF NOT EXISTS media (
281
- id TEXT PRIMARY KEY,
282
- filename TEXT NOT NULL UNIQUE,
283
- title TEXT,
284
- original_name TEXT NOT NULL,
285
- mime_type TEXT NOT NULL,
286
- file_size INTEGER NOT NULL,
287
- width INTEGER,
288
- height INTEGER,
289
- url TEXT NOT NULL UNIQUE,
290
- thumbnail_url TEXT,
291
- folder TEXT,
292
- provider TEXT NOT NULL,
293
- alt TEXT,
294
- caption TEXT,
295
- metadata TEXT,
296
- created_at TEXT DEFAULT (datetime('now')),
297
- updated_at TEXT DEFAULT (datetime('now'))
298
- )
299
- `);
300
- } catch (e) {
301
- // Table exists - try adding missing columns
302
- try {
303
- const tableInfo = db.prepare("PRAGMA table_info(media)").all();
304
- const existingColumns = new Set(tableInfo.map((c: any) => c.name));
305
-
306
- const requiredCols = [
307
- { name: "title", sql: "ALTER TABLE media ADD COLUMN title TEXT" },
308
- {
309
- name: "thumbnail_url",
310
- sql: "ALTER TABLE media ADD COLUMN thumbnail_url TEXT",
311
- },
312
- { name: "folder", sql: "ALTER TABLE media ADD COLUMN folder TEXT" },
313
- {
314
- name: "provider",
315
- sql: "ALTER TABLE media ADD COLUMN provider TEXT NOT NULL DEFAULT 'local'",
316
- },
317
- { name: "alt", sql: "ALTER TABLE media ADD COLUMN alt TEXT" },
318
- {
319
- name: "caption",
320
- sql: "ALTER TABLE media ADD COLUMN caption TEXT",
321
- },
322
- {
323
- name: "metadata",
324
- sql: "ALTER TABLE media ADD COLUMN metadata TEXT",
325
- },
326
- ];
327
-
328
- for (const col of requiredCols) {
329
- if (!existingColumns.has(col.name)) {
330
- try {
331
- db.exec(col.sql);
332
- } catch {}
333
- }
334
- }
335
- } catch {}
336
- }
337
-
338
- db.exec(`CREATE INDEX IF NOT EXISTS media_folder_idx ON media(folder)`);
339
- db.exec(
340
- `CREATE INDEX IF NOT EXISTS media_provider_idx ON media(provider)`,
341
- );
342
- db.exec(
343
- `CREATE INDEX IF NOT EXISTS media_filename_idx ON media(filename)`,
344
- );
345
-
346
- db.exec(`
347
- CREATE TABLE IF NOT EXISTS media_folders (
348
- path TEXT PRIMARY KEY,
349
- name TEXT NOT NULL,
350
- parent_path TEXT,
351
- created_at TEXT DEFAULT (datetime('now'))
352
- )
353
- `);
354
-
355
- db.close();
356
-
357
- migrationsRun = true;
358
- console.log(`[db] Migrations complete`);
359
- } else if (dbType === "mysql") {
360
- const mysql = await import("mysql2/promise");
361
- const connString =
362
- config.connectionString ||
363
- `mysql://${config.username}:${config.password}@${config.host}:${config.port}/${config.database}`;
364
- const pool = await mysql.createPool({ uri: connString });
365
-
366
- await pool.execute(`
367
- CREATE TABLE IF NOT EXISTS settings (
368
- \`key\` VARCHAR(255) PRIMARY KEY,
369
- \`value\` TEXT NOT NULL,
370
- \`description\` TEXT,
371
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
372
- )
373
- `);
374
-
375
- await pool.execute(`
376
- CREATE TABLE IF NOT EXISTS media (
377
- id VARCHAR(36) PRIMARY KEY,
378
- filename VARCHAR(255) NOT NULL UNIQUE,
379
- title VARCHAR(255),
380
- original_name VARCHAR(255) NOT NULL,
381
- mime_type VARCHAR(100) NOT NULL,
382
- file_size INTEGER NOT NULL,
383
- width INTEGER,
384
- height INTEGER,
385
- url TEXT NOT NULL UNIQUE,
386
- thumbnail_url TEXT,
387
- folder VARCHAR(255),
388
- provider VARCHAR(50) NOT NULL,
389
- alt TEXT,
390
- caption TEXT,
391
- metadata JSON,
392
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
393
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
394
- )
395
- `);
396
-
397
- await pool.execute(
398
- `CREATE INDEX IF NOT EXISTS media_folder_idx ON media(folder)`,
399
- );
400
- await pool.execute(
401
- `CREATE INDEX IF NOT EXISTS media_provider_idx ON media(provider)`,
402
- );
403
- await pool.execute(
404
- `CREATE INDEX IF NOT EXISTS media_filename_idx ON media(filename)`,
405
- );
406
-
407
- await pool.execute(`
408
- CREATE TABLE IF NOT EXISTS media_folders (
409
- path VARCHAR(500) PRIMARY KEY,
410
- name VARCHAR(255) NOT NULL,
411
- parent_path VARCHAR(500),
412
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
413
- )
414
- `);
415
-
416
- await pool.end();
417
-
418
- migrationsRun = true;
419
- console.log(`[db] Migrations complete`);
420
- } else if (dbType === "mongodb") {
421
- const { MongoClient } = await import("mongodb");
422
- const connString =
423
- config.connectionString || `mongodb://${config.host}:${config.port}`;
424
- const client = new MongoClient(connString);
425
- await client.connect();
426
- const db = client.db(config.database);
427
-
428
- const settingsCollection = db.collection("settings");
429
- await settingsCollection.createIndex("key", { unique: true });
430
-
431
- const mediaCollection = db.collection("media");
432
- await mediaCollection.createIndex("filename", { unique: true });
433
- await mediaCollection.createIndex("url", { unique: true });
434
- await mediaCollection.createIndex("folder");
435
- await mediaCollection.createIndex("provider");
436
- await mediaCollection.createIndex("created_at");
437
-
438
- const foldersCollection = db.collection("media_folders");
439
- await foldersCollection.createIndex("path", { unique: true });
440
-
441
- await client.close();
442
-
443
- migrationsRun = true;
444
- console.log(`[db] Migrations complete`);
445
- }
446
- } catch (error) {
447
- console.error(`[db] Migration error:`, error);
448
- }
449
- }
@@ -1,207 +0,0 @@
1
- import type { CollectionConfig } from "@kyro-cms/core";
2
- import type { DatabaseAdapter, DatabaseConfig } from "./adapter";
3
- import { MongoClient, Db } from "mongodb";
4
- import { randomBytes } from "crypto";
5
-
6
- export class MongoDBAdapter implements DatabaseAdapter {
7
- private client: MongoClient | null = null;
8
- private db: Db | null = null;
9
- private initialized = false;
10
-
11
- constructor(private config: DatabaseConfig) {}
12
-
13
- private async getDb(): Promise<Db> {
14
- if (!this.client) {
15
- const connectionString =
16
- this.config.connectionString ||
17
- `mongodb://${this.config.username}:${this.config.password}@${this.config.host}:${this.config.port}`;
18
- this.client = new MongoClient(connectionString);
19
- await this.client.connect();
20
- }
21
- if (!this.db) {
22
- this.db = this.client!.db(this.config.database || "kyro");
23
- }
24
- return this.db;
25
- }
26
-
27
- async connect(): Promise<void> {
28
- await this.getDb();
29
- }
30
-
31
- async disconnect(): Promise<void> {
32
- if (this.client) {
33
- await this.client.close();
34
- this.client = null;
35
- this.db = null;
36
- }
37
- }
38
-
39
- initialize(collections: Record<string, CollectionConfig>): void {
40
- if (this.initialized) return;
41
- this.initialized = true;
42
- }
43
-
44
- private getTimestamp(): string {
45
- return new Date().toISOString();
46
- }
47
-
48
- private async generateId(slug: string): Promise<string> {
49
- const db = await this.getDb();
50
- const count = await db
51
- .collection("documents")
52
- .countDocuments({ collection: slug });
53
- return `${slug}-${count + 1}`;
54
- }
55
-
56
- async find<T = any>(
57
- slug: string,
58
- options: { page?: number; limit?: number } = {},
59
- ): Promise<{
60
- docs: T[];
61
- totalDocs: number;
62
- totalPages: number;
63
- page: number;
64
- }> {
65
- const db = await this.getDb();
66
- const page = options.page || 1;
67
- const limit = options.limit || 25;
68
- const start = (page - 1) * limit;
69
-
70
- const totalDocs = await db
71
- .collection("documents")
72
- .countDocuments({ collection: slug });
73
- const docs = await db
74
- .collection("documents")
75
- .find({ collection: slug })
76
- .sort({ createdAt: -1 })
77
- .skip(start)
78
- .limit(limit)
79
- .toArray();
80
-
81
- return {
82
- docs: docs.map((d) => d.data as T),
83
- totalDocs,
84
- totalPages: Math.ceil(totalDocs / limit),
85
- page,
86
- };
87
- }
88
-
89
- async findById<T = any>(slug: string, id: string): Promise<T | null> {
90
- const db = await this.getDb();
91
- const doc = await db
92
- .collection("documents")
93
- .findOne({ collection: slug, id });
94
- return doc ? (doc.data as T) : null;
95
- }
96
-
97
- async create<T = any>(slug: string, data: Partial<T>): Promise<T> {
98
- const db = await this.getDb();
99
- const now = this.getTimestamp();
100
- const id = await this.generateId(slug);
101
- const newDoc = {
102
- id,
103
- collection: slug,
104
- data: {
105
- id,
106
- ...(data as Record<string, unknown>),
107
- createdAt: now,
108
- updatedAt: now,
109
- },
110
- createdAt: now,
111
- updatedAt: now,
112
- };
113
-
114
- await db.collection("documents").insertOne(newDoc);
115
- return newDoc.data as T;
116
- }
117
-
118
- async update<T = any>(
119
- slug: string,
120
- id: string,
121
- data: Partial<T>,
122
- ): Promise<T | null> {
123
- const db = await this.getDb();
124
- const existing = await this.findById<T>(slug, id);
125
- if (!existing) return null;
126
-
127
- const now = this.getTimestamp();
128
- const updated = { ...existing, ...data, id, updatedAt: now };
129
-
130
- await db
131
- .collection("documents")
132
- .updateOne(
133
- { collection: slug, id },
134
- { $set: { data: updated, updatedAt: now } },
135
- );
136
-
137
- return updated as T;
138
- }
139
-
140
- async delete(slug: string, id: string): Promise<boolean> {
141
- const db = await this.getDb();
142
- await db.collection("documents").deleteOne({ collection: slug, id });
143
- return true;
144
- }
145
-
146
- async findGlobal<T = any>(slug: string): Promise<T> {
147
- const db = await this.getDb();
148
- const global = await db.collection("globals").findOne({ slug });
149
- return global ? (global.data as T) : ({} as T);
150
- }
151
-
152
- async updateGlobal<T = any>(slug: string, data: Partial<T>): Promise<T> {
153
- const db = await this.getDb();
154
- const now = this.getTimestamp();
155
- const current = await this.findGlobal<T>(slug);
156
- const updated = { ...current, ...data };
157
-
158
- await db
159
- .collection("globals")
160
- .updateOne(
161
- { slug },
162
- { $set: { data: updated, updatedAt: now } },
163
- { upsert: true },
164
- );
165
-
166
- return updated as T;
167
- }
168
-
169
- async seedGlobal(slug: string, data: any): Promise<void> {
170
- const db = await this.getDb();
171
- const now = this.getTimestamp();
172
- await db
173
- .collection("globals")
174
- .updateOne(
175
- { slug },
176
- { $setOnInsert: { slug, data, updatedAt: now } },
177
- { upsert: true },
178
- );
179
- }
180
-
181
- async count(slug: string): Promise<number> {
182
- const db = await this.getDb();
183
- return db.collection("documents").countDocuments({ collection: slug });
184
- }
185
-
186
- async seed(slug: string, docs: any[]): Promise<void> {
187
- const db = await this.getDb();
188
- const now = this.getTimestamp();
189
- const insertDocs = docs.map((doc, i) => ({
190
- id: doc.id || `${slug}-${i + 1}`,
191
- collection: slug,
192
- data: {
193
- ...doc,
194
- createdAt: doc.createdAt || now,
195
- updatedAt: doc.updatedAt || now,
196
- },
197
- createdAt: doc.createdAt || now,
198
- updatedAt: doc.updatedAt || now,
199
- }));
200
-
201
- await db.collection("documents").insertMany(insertDocs);
202
- }
203
-
204
- async isSeeded(slug: string): Promise<boolean> {
205
- return (await this.count(slug)) > 0;
206
- }
207
- }