@kyro-cms/admin 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +149 -51
  2. package/package.json +52 -5
  3. package/src/collections/auth/index.ts +2 -2
  4. package/src/collections/portfolio/index.ts +343 -0
  5. package/src/components/ActionBar.tsx +153 -16
  6. package/src/components/Admin.tsx +136 -27
  7. package/src/components/ApiExplorer.tsx +325 -0
  8. package/src/components/ApiKeysManager.tsx +563 -0
  9. package/src/components/AuditLogsPage.tsx +664 -0
  10. package/src/components/AutoForm.tsx +1417 -661
  11. package/src/components/BrandingHub.tsx +267 -0
  12. package/src/components/BulkActionsBar.tsx +3 -3
  13. package/src/components/CreateView.tsx +3 -3
  14. package/src/components/Dashboard.tsx +393 -0
  15. package/src/components/DetailView.tsx +199 -57
  16. package/src/components/DeveloperCenter.tsx +403 -0
  17. package/src/components/EnhancedListView.tsx +786 -0
  18. package/src/components/GraphQLExplorer.tsx +675 -0
  19. package/src/components/GraphQLPlayground.tsx +627 -0
  20. package/src/components/ListView.tsx +191 -53
  21. package/src/components/MediaGallery.tsx +1569 -0
  22. package/src/components/Modal.tsx +149 -0
  23. package/src/components/RestPlayground.tsx +951 -0
  24. package/src/components/Sidebar.astro +237 -0
  25. package/src/components/UserManagement.tsx +204 -0
  26. package/src/components/VersionHistoryPanel.tsx +3 -3
  27. package/src/components/WebhookManager.tsx +608 -0
  28. package/src/components/blocks/AccordionBlock.tsx +97 -0
  29. package/src/components/blocks/ArrayBlock.tsx +75 -0
  30. package/src/components/blocks/BlockEditModal.MARKER +12 -0
  31. package/src/components/blocks/BlockEditModal.tsx +774 -0
  32. package/src/components/blocks/ButtonBlock.tsx +165 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +551 -0
  34. package/src/components/blocks/CodeBlock.tsx +66 -0
  35. package/src/components/blocks/ColumnsBlock.tsx +151 -0
  36. package/src/components/blocks/DividerBlock.tsx +43 -0
  37. package/src/components/blocks/FileBlock.tsx +64 -0
  38. package/src/components/blocks/HeadingBlock.tsx +81 -0
  39. package/src/components/blocks/HeroBlock.tsx +157 -0
  40. package/src/components/blocks/ImageBlock.tsx +83 -0
  41. package/src/components/blocks/LinkBlock.tsx +71 -0
  42. package/src/components/blocks/ListBlock.tsx +39 -0
  43. package/src/components/blocks/ParagraphBlock.tsx +61 -0
  44. package/src/components/blocks/RelationshipBlock.tsx +279 -0
  45. package/src/components/blocks/VStackBlock.tsx +75 -0
  46. package/src/components/blocks/VideoBlock.tsx +45 -0
  47. package/src/components/blocks/index.ts +10 -0
  48. package/src/components/fields/BlocksField.tsx +323 -0
  49. package/src/components/fields/CheckboxField.tsx +15 -9
  50. package/src/components/fields/CodeField.tsx +234 -0
  51. package/src/components/fields/DateField.tsx +38 -11
  52. package/src/components/fields/EditorClient.tsx +271 -0
  53. package/src/components/fields/FileField.tsx +390 -0
  54. package/src/components/fields/HybridContentField.tsx +109 -0
  55. package/src/components/fields/ImageField.tsx +429 -0
  56. package/src/components/fields/JSONField.tsx +361 -0
  57. package/src/components/fields/MarkdownField.tsx +282 -0
  58. package/src/components/fields/NumberField.tsx +42 -12
  59. package/src/components/fields/PortableTextField.tsx +143 -0
  60. package/src/components/fields/PortableTextRenderer.tsx +68 -0
  61. package/src/components/fields/RelationshipField.tsx +231 -59
  62. package/src/components/fields/SelectField.tsx +25 -15
  63. package/src/components/fields/TextField.tsx +45 -14
  64. package/src/components/fields/extensions/blockComponents.tsx +237 -0
  65. package/src/components/fields/extensions/blocksStore.ts +273 -0
  66. package/src/components/fields/index.ts +13 -0
  67. package/src/components/index.ts +1 -2
  68. package/src/components/layout/Header.tsx +2 -2
  69. package/src/components/layout/Layout.tsx +2 -2
  70. package/src/components/ui/Badge.tsx +9 -4
  71. package/src/components/ui/BlockDrawer.tsx +79 -0
  72. package/src/components/ui/Button.tsx +1 -1
  73. package/src/components/ui/CommandPalette.tsx +362 -0
  74. package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
  75. package/src/components/ui/Dropdown.tsx +1 -1
  76. package/src/components/ui/Modal.tsx +37 -12
  77. package/src/components/ui/PromptModal.tsx +94 -0
  78. package/src/components/ui/SlidePanel.tsx +43 -16
  79. package/src/components/ui/Toast.tsx +80 -14
  80. package/src/env.d.ts +16 -0
  81. package/src/env.ts +20 -0
  82. package/src/index.ts +0 -1
  83. package/src/layouts/AdminLayout.astro +164 -170
  84. package/src/layouts/AuthLayout.astro +50 -0
  85. package/src/lib/MediaService.ts +541 -0
  86. package/src/lib/auth/sqlite-adapter.ts +319 -0
  87. package/src/lib/config.ts +22 -6
  88. package/src/lib/dataStore.ts +132 -74
  89. package/src/lib/db/adapter.ts +54 -0
  90. package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
  91. package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
  92. package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
  93. package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
  94. package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
  95. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
  96. package/src/lib/db/index.ts +449 -0
  97. package/src/lib/db/mongodb-adapter.ts +207 -0
  98. package/src/lib/db/mongodb-auth-adapter.ts +305 -0
  99. package/src/lib/db/schema/mysql-auth.ts +113 -0
  100. package/src/lib/db/schema/mysql-content.ts +20 -0
  101. package/src/lib/db/schema/postgres-auth.ts +116 -0
  102. package/src/lib/db/schema/postgres-content.ts +35 -0
  103. package/src/lib/db/schema/postgres-media.ts +52 -0
  104. package/src/lib/db/schema/postgres-settings.ts +11 -0
  105. package/src/lib/db/schema/sqlite-auth.ts +112 -0
  106. package/src/lib/db/schema/sqlite-content.ts +20 -0
  107. package/src/lib/graphql/index.ts +1 -0
  108. package/src/lib/graphql/schema.ts +443 -0
  109. package/src/lib/rate-limit.ts +267 -0
  110. package/src/lib/storage.ts +374 -0
  111. package/src/lib/store.ts +85 -0
  112. package/src/middleware.ts +116 -28
  113. package/src/pages/[collection]/[id].astro +178 -122
  114. package/src/pages/[collection]/index.astro +24 -156
  115. package/src/pages/admin/api-explorer.astro +98 -0
  116. package/src/pages/admin/graphql-explorer.astro +40 -0
  117. package/src/pages/admin/graphql.astro +97 -0
  118. package/src/pages/admin/index.astro +286 -0
  119. package/src/pages/admin/keys.astro +8 -0
  120. package/src/pages/admin/rest-playground.astro +44 -0
  121. package/src/pages/admin/webhooks.astro +8 -0
  122. package/src/pages/api/[collection]/[id]/publish.ts +44 -0
  123. package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
  124. package/src/pages/api/[collection]/[id]/versions.ts +36 -0
  125. package/src/pages/api/[collection]/[id].ts +102 -159
  126. package/src/pages/api/[collection]/index.ts +151 -230
  127. package/src/pages/api/auth/[id].ts +48 -69
  128. package/src/pages/api/auth/audit-logs.ts +20 -43
  129. package/src/pages/api/auth/login.ts +159 -45
  130. package/src/pages/api/auth/logout.ts +50 -20
  131. package/src/pages/api/auth/refresh.ts +119 -0
  132. package/src/pages/api/auth/register.ts +110 -40
  133. package/src/pages/api/auth/users.ts +22 -97
  134. package/src/pages/api/collections.ts +59 -0
  135. package/src/pages/api/globals/[slug]/test.ts +172 -0
  136. package/src/pages/api/globals/[slug].ts +42 -0
  137. package/src/pages/api/graphql.ts +90 -0
  138. package/src/pages/api/health.ts +417 -40
  139. package/src/pages/api/keys/[id].ts +26 -0
  140. package/src/pages/api/keys/index.ts +75 -0
  141. package/src/pages/api/media/[id].ts +309 -0
  142. package/src/pages/api/media/folders.ts +609 -0
  143. package/src/pages/api/media/index.ts +146 -0
  144. package/src/pages/api/media/resize.ts +267 -0
  145. package/src/pages/api/search.ts +82 -0
  146. package/src/pages/api/slug-availability.ts +70 -0
  147. package/src/pages/api/storage-config.ts +20 -0
  148. package/src/pages/api/storage-status.ts +206 -0
  149. package/src/pages/api/upload.ts +334 -0
  150. package/src/pages/api/webhooks/index.ts +71 -0
  151. package/src/pages/audit/index.astro +2 -104
  152. package/src/pages/login.astro +82 -0
  153. package/src/pages/media.astro +10 -0
  154. package/src/pages/preview/[collection]/[id].astro +178 -0
  155. package/src/pages/register.astro +102 -0
  156. package/src/pages/roles/index.astro +21 -21
  157. package/src/pages/settings/[slug].astro +162 -0
  158. package/src/pages/settings/index.astro +9 -0
  159. package/src/pages/users/[id].astro +29 -21
  160. package/src/pages/users/index.astro +22 -17
  161. package/src/pages/users/new.astro +18 -17
  162. package/src/styles/main.css +553 -128
  163. package/src/components/layout/Sidebar.tsx +0 -497
  164. package/src/pages/index.astro +0 -225
@@ -1,210 +1,182 @@
1
1
  import type { APIRoute } from "astro";
2
2
  import { dataStore } from "@/lib/dataStore";
3
3
  import { collections } from "@/lib/config";
4
+ import { getAuthAdapter } from "../../../lib/db";
4
5
 
5
6
  dataStore.initialize(collections);
6
7
 
7
- const AUTH_COLLECTIONS = ["users", "roles", "audit_logs"];
8
-
9
- async function getAuthApi() {
10
- const { RedisAuthAdapter } = await import("@kyro-cms/core");
11
- return new RedisAuthAdapter({
12
- url: process.env.REDIS_URL || "redis://localhost:6379",
13
- tls: process.env.REDIS_TLS === "true",
14
- });
15
- }
16
-
17
- export const GET: APIRoute = async ({ params, url }) => {
18
- const collection = params.collection as string;
19
-
20
- if (AUTH_COLLECTIONS.includes(collection)) {
21
- const page = parseInt(url.searchParams.get("page") || "1");
22
- const limit = parseInt(url.searchParams.get("limit") || "25");
23
- const search = url.searchParams.get("search") || "";
24
-
25
- try {
26
- const adapter = await getAuthApi();
27
- await adapter.connect();
28
-
29
- if (collection === "users") {
30
- const pattern = search ? `*${search.toLowerCase()}*` : "*";
31
- let cursor = "0";
32
- const users: any[] = [];
33
- const seenIds = new Set<string>();
34
-
35
- do {
36
- const [nextCursor, keys] = await (adapter as any).redis.scan(
37
- cursor,
38
- "MATCH",
39
- "kyro:auth:users:email:*",
40
- "COUNT",
41
- 100,
42
- );
43
- cursor = nextCursor;
8
+ const initSeedData = async () => {
9
+ const store = dataStore;
10
+
11
+ // Seed products (for ecommerce)
12
+ if (!(await store.isSeeded("products"))) {
13
+ await store.seed("products", [
14
+ {
15
+ title: "Kyro Stealth Hoodie",
16
+ slug: "kyro-stealth-hoodie",
17
+ sku: "KSH-001",
18
+ price: 89.99,
19
+ status: "active",
20
+ inventory: 50,
21
+ description: "Premium heavy-weight cotton hoodie.",
22
+ category: "apparel-1",
23
+ },
24
+ ]);
25
+ }
44
26
 
45
- for (const key of keys) {
46
- const userId = await (adapter as any).redis.get(key);
47
- if (userId && !seenIds.has(userId)) {
48
- seenIds.add(userId);
49
- const user = await adapter.findUserById(userId);
50
- if (user) {
51
- const { passwordHash, ...safeUser } = user;
52
- users.push(safeUser);
53
- }
54
- }
55
- }
56
- } while (cursor !== "0");
27
+ // Seed pages
28
+ if (!(await store.isSeeded("pages"))) {
29
+ await store.seed("pages", [
30
+ {
31
+ title: "About Us",
32
+ slug: "about",
33
+ content: "<p>Welcome to our about page!</p>",
34
+ status: "published",
35
+ },
36
+ {
37
+ title: "Contact",
38
+ slug: "contact",
39
+ content: "<p>Get in touch with us!</p>",
40
+ status: "published",
41
+ },
42
+ ]);
43
+ }
57
44
 
58
- const totalDocs = users.length;
59
- const startIndex = (page - 1) * limit;
60
- const paginatedUsers = users.slice(startIndex, startIndex + limit);
45
+ // Seed posts (for blog content)
46
+ if (!(await store.isSeeded("posts"))) {
47
+ await store.seed("posts", [
48
+ {
49
+ title: "Welcome to Kyro CMS",
50
+ slug: "welcome-to-kyro-cms",
51
+ content:
52
+ "<p>Welcome to your new CMS! This is your first blog post.</p>",
53
+ excerpt: "Welcome to your new CMS!",
54
+ status: "published",
55
+ publishedAt: new Date().toISOString(),
56
+ },
57
+ ]);
58
+ }
61
59
 
62
- await adapter.disconnect();
60
+ // Seed categories
61
+ if (!(await store.isSeeded("categories"))) {
62
+ await store.seed("categories", [
63
+ { title: "General", slug: "general", description: "General category" },
64
+ { title: "News", slug: "news", description: "News and announcements" },
65
+ { title: "Updates", slug: "updates", description: "Product updates" },
66
+ ]);
67
+ }
63
68
 
64
- return new Response(
65
- JSON.stringify({
66
- docs: paginatedUsers,
67
- totalDocs,
68
- page,
69
- limit,
70
- totalPages: Math.ceil(totalDocs / limit),
71
- }),
72
- { status: 200, headers: { "Content-Type": "application/json" } },
73
- );
74
- }
69
+ // Seed navigation
70
+ if (!(await store.isSeeded("navigation"))) {
71
+ await store.seed("navigation", [
72
+ { label: "Home", url: "/", location: "header", order: 1 },
73
+ { label: "Blog", url: "/blog", location: "header", order: 2 },
74
+ { label: "About", url: "/about", location: "header", order: 3 },
75
+ { label: "Contact", url: "/contact", location: "header", order: 4 },
76
+ { label: "Privacy", url: "/privacy", location: "footer", order: 1 },
77
+ { label: "Terms", url: "/terms", location: "footer", order: 2 },
78
+ ]);
79
+ }
75
80
 
76
- if (collection === "roles") {
77
- const defaultRoles = [
78
- {
79
- id: "super_admin",
80
- name: "super_admin",
81
- level: 100,
82
- inherits: ["admin"],
83
- description: "Full system access across all tenants",
84
- },
85
- {
86
- id: "admin",
87
- name: "admin",
88
- level: 90,
89
- inherits: ["editor"],
90
- description: "Full tenant access with all content permissions",
91
- },
92
- {
93
- id: "editor",
94
- name: "editor",
95
- level: 70,
96
- inherits: ["author"],
97
- description: "Edit and publish all content",
98
- },
99
- {
100
- id: "author",
101
- name: "author",
102
- level: 50,
103
- inherits: ["customer"],
104
- description: "Create and edit own content",
105
- },
106
- {
107
- id: "customer",
108
- name: "customer",
109
- level: 30,
110
- inherits: [],
111
- description: "Access own data and make purchases",
112
- },
113
- {
114
- id: "guest",
115
- name: "guest",
116
- level: 10,
117
- inherits: [],
118
- description: "Public read-only access",
119
- },
120
- ];
81
+ // Seed site settings
82
+ const siteGlobal = await store.findGlobal("site-settings");
83
+ if (!siteGlobal.siteName) {
84
+ await store.seedGlobal("site-settings", {
85
+ siteName: "Kyro CMS",
86
+ siteDescription: "A modern, powerful headless CMS built with Astro.",
87
+ allowRegistration: true,
88
+ });
89
+ }
121
90
 
122
- await adapter.disconnect();
91
+ // Seed SEO settings
92
+ const seoGlobal = await store.findGlobal("seo-settings");
93
+ if (!seoGlobal.defaultTitle) {
94
+ await store.seedGlobal("seo-settings", {
95
+ defaultTitle: "Kyro CMS - The Performance CMS",
96
+ titleTemplate: "%s | Kyro CMS",
97
+ defaultDescription:
98
+ "High-performance content management for the modern web.",
99
+ });
100
+ }
101
+ };
123
102
 
124
- return new Response(
125
- JSON.stringify({
126
- docs: defaultRoles,
127
- totalDocs: defaultRoles.length,
128
- page,
129
- limit,
130
- totalPages: 1,
131
- }),
132
- { status: 200, headers: { "Content-Type": "application/json" } },
133
- );
134
- }
103
+ initSeedData().catch(console.error);
135
104
 
136
- if (collection === "audit_logs") {
137
- const logs = await (adapter as any).redis.keys("kyro:auth:audit:*");
138
- const auditLogs: any[] = [];
105
+ const AUTH_COLLECTIONS = ["users", "roles", "audit_logs"];
139
106
 
140
- for (const key of logs.slice(0, 100)) {
141
- const logData = await (adapter as any).redis.hgetall(key);
142
- if (logData) {
143
- auditLogs.push({
144
- id: logData.id,
145
- action: logData.action,
146
- userId: logData.userId,
147
- userEmail: logData.userEmail,
148
- role: logData.role,
149
- resource: logData.resource,
150
- ipAddress: logData.ipAddress,
151
- userAgent: logData.userAgent,
152
- success: logData.success === "true",
153
- error: logData.error,
154
- timestamp: logData.timestamp,
155
- });
156
- }
157
- }
107
+ async function getAuthAdapterLocal() {
108
+ return await getAuthAdapter();
109
+ }
158
110
 
159
- const sortedLogs = auditLogs.sort(
160
- (a, b) =>
161
- new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
162
- );
111
+ export const GET: APIRoute = async ({ params, url }) => {
112
+ const collection = params.collection as string;
113
+ const page = parseInt(url.searchParams.get("page") || "1");
114
+ const limit = parseInt(url.searchParams.get("limit") || "25");
163
115
 
164
- const totalDocs = sortedLogs.length;
165
- const startIndex = (page - 1) * limit;
166
- const paginatedLogs = sortedLogs.slice(startIndex, startIndex + limit);
116
+ if (AUTH_COLLECTIONS.includes(collection)) {
117
+ const adapter = await getAuthAdapterLocal();
167
118
 
168
- await adapter.disconnect();
119
+ if (collection === "users") {
120
+ const users = await adapter.findAllUsers();
121
+ const safeUsers = users.map(({ passwordHash, ...user }) => user);
122
+ return new Response(
123
+ JSON.stringify({
124
+ docs: safeUsers,
125
+ totalDocs: safeUsers.length,
126
+ page,
127
+ limit,
128
+ totalPages: 1,
129
+ }),
130
+ { status: 200, headers: { "Content-Type": "application/json" } },
131
+ );
132
+ }
169
133
 
170
- return new Response(
171
- JSON.stringify({
172
- docs: paginatedLogs,
173
- totalDocs,
174
- page,
175
- limit,
176
- totalPages: Math.ceil(totalDocs / limit) || 1,
177
- }),
178
- { status: 200, headers: { "Content-Type": "application/json" } },
179
- );
180
- }
134
+ if (collection === "roles") {
135
+ const roles = await adapter.findUserRoles();
136
+ return new Response(
137
+ JSON.stringify({
138
+ docs: roles,
139
+ totalDocs: roles.length,
140
+ page,
141
+ limit,
142
+ totalPages: 1,
143
+ }),
144
+ { status: 200, headers: { "Content-Type": "application/json" } },
145
+ );
146
+ }
181
147
 
182
- await adapter.disconnect();
183
- } catch (error) {
184
- console.error(`Error fetching ${collection}:`, error);
148
+ if (collection === "audit_logs") {
149
+ const offset = (page - 1) * limit;
150
+ const { logs, total } = await adapter.findAuditLogs({
151
+ limit,
152
+ offset,
153
+ });
185
154
  return new Response(
186
155
  JSON.stringify({
187
- error: `Failed to fetch ${collection}`,
188
- docs: [],
189
- totalDocs: 0,
156
+ docs: logs,
157
+ totalDocs: total,
158
+ page,
159
+ limit,
160
+ totalPages: Math.ceil(total / limit),
190
161
  }),
191
162
  { status: 200, headers: { "Content-Type": "application/json" } },
192
163
  );
193
164
  }
194
165
  }
195
166
 
196
- const page = parseInt(url.searchParams.get("page") || "1");
197
- const limit = parseInt(url.searchParams.get("limit") || "25");
198
-
199
167
  try {
200
- const result = dataStore.find(collection, { page, limit });
168
+ const result = await dataStore.find(collection, { page, limit });
201
169
  return new Response(JSON.stringify(result), {
202
170
  status: 200,
203
171
  headers: { "Content-Type": "application/json" },
204
172
  });
205
173
  } catch (error) {
206
174
  return new Response(
207
- JSON.stringify({ error: "Failed to fetch documents" }),
175
+ JSON.stringify({
176
+ error: "Failed to fetch documents",
177
+ docs: [],
178
+ totalDocs: 0,
179
+ }),
208
180
  { status: 500, headers: { "Content-Type": "application/json" } },
209
181
  );
210
182
  }
@@ -214,68 +186,17 @@ export const POST: APIRoute = async ({ params, request }) => {
214
186
  const collection = params.collection as string;
215
187
 
216
188
  if (AUTH_COLLECTIONS.includes(collection)) {
217
- try {
218
- const adapter = await getAuthApi();
219
- await adapter.connect();
220
-
221
- const body = await request.json();
222
-
223
- if (collection === "users") {
224
- const { email, password, role, tenantId } = body;
225
-
226
- if (!email || !password) {
227
- await adapter.disconnect();
228
- return new Response(
229
- JSON.stringify({ error: "Email and password are required" }),
230
- { status: 400, headers: { "Content-Type": "application/json" } },
231
- );
232
- }
233
-
234
- const existing = await adapter.findUserByEmail(email);
235
- if (existing) {
236
- await adapter.disconnect();
237
- return new Response(
238
- JSON.stringify({ error: "Email already exists" }),
239
- { status: 400, headers: { "Content-Type": "application/json" } },
240
- );
241
- }
242
-
243
- const passwordHash = await adapter.hashPassword(password);
244
- const user = await adapter.createUser({
245
- email,
246
- passwordHash,
247
- role: role || "customer",
248
- tenantId,
249
- });
250
-
251
- await adapter.disconnect();
252
-
253
- const { passwordHash: _, ...safeUser } = user;
254
- return new Response(JSON.stringify({ data: safeUser }), {
255
- status: 201,
256
- headers: { "Content-Type": "application/json" },
257
- });
258
- }
259
-
260
- await adapter.disconnect();
261
- return new Response(
262
- JSON.stringify({
263
- error: `Collection ${collection} does not support POST`,
264
- }),
265
- { status: 405, headers: { "Content-Type": "application/json" } },
266
- );
267
- } catch (error) {
268
- console.error(`Error creating ${collection}:`, error);
269
- return new Response(
270
- JSON.stringify({ error: `Failed to create ${collection}` }),
271
- { status: 500, headers: { "Content-Type": "application/json" } },
272
- );
273
- }
189
+ return new Response(
190
+ JSON.stringify({
191
+ error: `Collection ${collection} does not support POST`,
192
+ }),
193
+ { status: 405, headers: { "Content-Type": "application/json" } },
194
+ );
274
195
  }
275
196
 
276
197
  try {
277
198
  const body = await request.json();
278
- const doc = dataStore.create(collection, body);
199
+ const doc = await dataStore.create(collection, body);
279
200
  return new Response(JSON.stringify({ data: doc }), {
280
201
  status: 201,
281
202
  headers: { "Content-Type": "application/json" },
@@ -1,30 +1,12 @@
1
1
  import type { APIRoute } from "astro";
2
- import { RedisAuthAdapter } from "@kyro-cms/core";
3
- import { AuditLogger } from "@kyro-cms/core";
4
- import { createAuditContext } from "@kyro-cms/core";
5
- import bcrypt from "bcryptjs";
6
-
7
- const redisAdapter = new RedisAuthAdapter({
8
- url: process.env.REDIS_URL || "redis://localhost:6379",
9
- });
10
-
11
- const auditLogger = new AuditLogger(redisAdapter as any);
12
-
13
- async function ensureConnection() {
14
- try {
15
- await redisAdapter.connect();
16
- } catch (e) {
17
- // Connection might already be established
18
- }
19
- }
2
+ import { getAuthAdapter } from "../../../lib/db";
20
3
 
21
4
  export const GET: APIRoute = async ({ params }) => {
22
- await ensureConnection();
23
-
24
5
  const { id } = params;
6
+ const adapter = await getAuthAdapter();
25
7
 
26
8
  try {
27
- const user = await redisAdapter.findUserById(id!);
9
+ const user = await adapter.findUserById(id!);
28
10
 
29
11
  if (!user) {
30
12
  return new Response(JSON.stringify({ error: "User not found" }), {
@@ -33,8 +15,7 @@ export const GET: APIRoute = async ({ params }) => {
33
15
  });
34
16
  }
35
17
 
36
- const { passwordHash, ...safeUser } = user;
37
- return new Response(JSON.stringify({ data: safeUser }), {
18
+ return new Response(JSON.stringify({ data: user }), {
38
19
  status: 200,
39
20
  headers: { "Content-Type": "application/json" },
40
21
  });
@@ -48,46 +29,26 @@ export const GET: APIRoute = async ({ params }) => {
48
29
  };
49
30
 
50
31
  export const PUT: APIRoute = async ({ params, request }) => {
51
- await ensureConnection();
52
-
53
32
  const { id } = params;
54
- const { ipAddress, userAgent } = createAuditContext(request as any);
33
+ const adapter = await getAuthAdapter();
55
34
 
56
35
  try {
57
36
  const body = await request.json();
58
- const { email, role, tenantId, locked, emailVerified } = body;
37
+ const { email, role } = body;
59
38
 
60
- const existing = await redisAdapter.findUserById(id!);
61
- if (!existing) {
39
+ const user = await adapter.updateUser(id!, {
40
+ email,
41
+ role,
42
+ });
43
+
44
+ if (!user) {
62
45
  return new Response(JSON.stringify({ error: "User not found" }), {
63
46
  status: 404,
64
47
  headers: { "Content-Type": "application/json" },
65
48
  });
66
49
  }
67
50
 
68
- const updateData: any = {};
69
- if (email !== undefined) updateData.email = email;
70
- if (role !== undefined) updateData.role = role;
71
- if (tenantId !== undefined) updateData.tenantId = tenantId;
72
- if (locked !== undefined) updateData.locked = locked;
73
- if (emailVerified !== undefined) updateData.emailVerified = emailVerified;
74
-
75
- const user = await redisAdapter.updateUser(id!, updateData);
76
-
77
- await auditLogger.log({
78
- action: "user_update",
79
- userId: id,
80
- userEmail: existing.email,
81
- role: existing.role,
82
- resource: "users",
83
- ipAddress,
84
- userAgent,
85
- success: true,
86
- metadata: { updateData },
87
- });
88
-
89
- const { passwordHash, ...safeUser } = user!;
90
- return new Response(JSON.stringify({ data: safeUser }), {
51
+ return new Response(JSON.stringify({ data: user }), {
91
52
  status: 200,
92
53
  headers: { "Content-Type": "application/json" },
93
54
  });
@@ -100,33 +61,51 @@ export const PUT: APIRoute = async ({ params, request }) => {
100
61
  }
101
62
  };
102
63
 
103
- export const DELETE: APIRoute = async ({ params, request }) => {
104
- await ensureConnection();
105
-
64
+ export const PATCH: APIRoute = async ({ params, request }) => {
106
65
  const { id } = params;
107
- const { ipAddress, userAgent } = createAuditContext(request as any);
66
+ const adapter = await getAuthAdapter();
108
67
 
109
68
  try {
110
- const existing = await redisAdapter.findUserById(id!);
111
- if (!existing) {
69
+ const body = await request.json();
70
+ const { locked } = body;
71
+
72
+ const user = await adapter.updateUser(id!, {
73
+ locked,
74
+ });
75
+
76
+ if (!user) {
112
77
  return new Response(JSON.stringify({ error: "User not found" }), {
113
78
  status: 404,
114
79
  headers: { "Content-Type": "application/json" },
115
80
  });
116
81
  }
117
82
 
118
- await redisAdapter.deleteUser(id!);
119
-
120
- await auditLogger.log({
121
- action: "user_delete",
122
- userId: id,
123
- userEmail: existing.email,
124
- role: existing.role,
125
- resource: "users",
126
- ipAddress,
127
- userAgent,
128
- success: true,
83
+ return new Response(JSON.stringify({ data: user }), {
84
+ status: 200,
85
+ headers: { "Content-Type": "application/json" },
129
86
  });
87
+ } catch (error) {
88
+ console.error("Error setting user locked status:", error);
89
+ return new Response(JSON.stringify({ error: "Failed to update user" }), {
90
+ status: 500,
91
+ headers: { "Content-Type": "application/json" },
92
+ });
93
+ }
94
+ };
95
+
96
+ export const DELETE: APIRoute = async ({ params }) => {
97
+ const { id } = params;
98
+ const adapter = await getAuthAdapter();
99
+
100
+ try {
101
+ const success = await adapter.deleteUser(id!);
102
+
103
+ if (!success) {
104
+ return new Response(JSON.stringify({ error: "User not found" }), {
105
+ status: 404,
106
+ headers: { "Content-Type": "application/json" },
107
+ });
108
+ }
130
109
 
131
110
  return new Response(JSON.stringify({ success: true }), {
132
111
  status: 200,