@kyro-cms/admin 0.1.6 → 0.1.8

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 (179) hide show
  1. package/README.md +149 -51
  2. package/package.json +54 -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 +137 -28
  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 +2155 -770
  11. package/src/components/BrandingHub.tsx +267 -0
  12. package/src/components/BulkActionsBar.tsx +3 -3
  13. package/src/components/CreateView.tsx +4 -4
  14. package/src/components/Dashboard.tsx +393 -0
  15. package/src/components/DetailView.tsx +200 -58
  16. package/src/components/DeveloperCenter.tsx +403 -0
  17. package/src/components/EnhancedListView.tsx +890 -0
  18. package/src/components/GraphQLExplorer.tsx +675 -0
  19. package/src/components/GraphQLPlayground.tsx +627 -0
  20. package/src/components/ListView.tsx +192 -54
  21. package/src/components/MediaGallery.tsx +1569 -0
  22. package/src/components/Modal.tsx +206 -0
  23. package/src/components/RestPlayground.tsx +951 -0
  24. package/src/components/Sidebar.astro +237 -0
  25. package/src/components/ThemeProvider.tsx +8 -2
  26. package/src/components/UserManagement.tsx +204 -0
  27. package/src/components/VersionHistoryPanel.tsx +3 -3
  28. package/src/components/WebhookManager.tsx +608 -0
  29. package/src/components/blocks/AccordionBlock.tsx +65 -0
  30. package/src/components/blocks/ArrayBlock.tsx +84 -0
  31. package/src/components/blocks/BlockEditModal.tsx +363 -0
  32. package/src/components/blocks/ButtonBlock.tsx +64 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +551 -0
  34. package/src/components/blocks/CodeBlock.tsx +114 -0
  35. package/src/components/blocks/ColumnsBlock.tsx +93 -0
  36. package/src/components/blocks/DividerBlock.tsx +43 -0
  37. package/src/components/blocks/FileBlock.tsx +63 -0
  38. package/src/components/blocks/HeadingBlock.tsx +59 -0
  39. package/src/components/blocks/HeroBlock.tsx +99 -0
  40. package/src/components/blocks/ImageBlock.tsx +82 -0
  41. package/src/components/blocks/LinkBlock.tsx +65 -0
  42. package/src/components/blocks/ListBlock.tsx +60 -0
  43. package/src/components/blocks/ParagraphBlock.tsx +61 -0
  44. package/src/components/blocks/RelationshipBlock.tsx +72 -0
  45. package/src/components/blocks/RichTextBlock.tsx +66 -0
  46. package/src/components/blocks/VStackBlock.tsx +61 -0
  47. package/src/components/blocks/VideoBlock.tsx +65 -0
  48. package/src/components/blocks/index.ts +10 -0
  49. package/src/components/fields/AccordionField.tsx +213 -0
  50. package/src/components/fields/ArrayField.tsx +241 -0
  51. package/src/components/fields/BlocksField.tsx +323 -0
  52. package/src/components/fields/ButtonField.tsx +53 -0
  53. package/src/components/fields/CheckboxField.tsx +18 -8
  54. package/src/components/fields/ChildrenField.tsx +48 -0
  55. package/src/components/fields/CodeField.tsx +294 -0
  56. package/src/components/fields/ColumnsField.tsx +137 -0
  57. package/src/components/fields/DateField.tsx +24 -12
  58. package/src/components/fields/EditorClient.tsx +537 -0
  59. package/src/components/fields/HeadingField.tsx +31 -0
  60. package/src/components/fields/HeroField.tsx +101 -0
  61. package/src/components/fields/JSONField.tsx +341 -0
  62. package/src/components/fields/LinkField.tsx +81 -0
  63. package/src/components/fields/ListField.tsx +74 -0
  64. package/src/components/fields/MarkdownField.tsx +260 -0
  65. package/src/components/fields/NumberField.tsx +25 -13
  66. package/src/components/fields/PortableTextField.tsx +155 -0
  67. package/src/components/fields/PortableTextRenderer.tsx +68 -0
  68. package/src/components/fields/RelationshipBlockField.tsx +233 -0
  69. package/src/components/fields/RelationshipField.tsx +278 -60
  70. package/src/components/fields/SelectField.tsx +28 -16
  71. package/src/components/fields/TextField.tsx +31 -15
  72. package/src/components/fields/UploadField.tsx +613 -0
  73. package/src/components/fields/VideoField.tsx +73 -0
  74. package/src/components/fields/extensions/blockComponents.tsx +247 -0
  75. package/src/components/fields/extensions/blocksStore.ts +273 -0
  76. package/src/components/fields/index.ts +24 -0
  77. package/src/components/index.ts +1 -2
  78. package/src/components/layout/Header.tsx +2 -2
  79. package/src/components/layout/Layout.tsx +3 -3
  80. package/src/components/ui/Badge.tsx +9 -4
  81. package/src/components/ui/BlockDrawer.tsx +79 -0
  82. package/src/components/ui/Button.tsx +1 -1
  83. package/src/components/ui/CommandPalette.tsx +362 -0
  84. package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
  85. package/src/components/ui/Dropdown.tsx +1 -1
  86. package/src/components/ui/Modal.tsx +37 -12
  87. package/src/components/ui/PromptModal.tsx +94 -0
  88. package/src/components/ui/SlidePanel.tsx +43 -16
  89. package/src/components/ui/Toast.tsx +80 -14
  90. package/src/env.d.ts +16 -0
  91. package/src/env.ts +20 -0
  92. package/src/index.ts +0 -1
  93. package/src/layouts/AdminLayout.astro +164 -170
  94. package/src/layouts/AuthLayout.astro +23 -6
  95. package/src/lib/MediaService.ts +541 -0
  96. package/src/lib/api.ts +163 -0
  97. package/src/lib/auth/sqlite-adapter.ts +319 -0
  98. package/src/lib/config.ts +23 -7
  99. package/src/lib/dataStore.ts +188 -73
  100. package/src/lib/date-utils.ts +69 -0
  101. package/src/lib/db/adapter.ts +54 -0
  102. package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
  103. package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
  104. package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
  105. package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
  106. package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
  107. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
  108. package/src/lib/db/index.ts +449 -0
  109. package/src/lib/db/mongodb-adapter.ts +207 -0
  110. package/src/lib/db/mongodb-auth-adapter.ts +305 -0
  111. package/src/lib/db/schema/mysql-auth.ts +113 -0
  112. package/src/lib/db/schema/mysql-content.ts +20 -0
  113. package/src/lib/db/schema/postgres-auth.ts +116 -0
  114. package/src/lib/db/schema/postgres-content.ts +35 -0
  115. package/src/lib/db/schema/postgres-media.ts +52 -0
  116. package/src/lib/db/schema/postgres-settings.ts +11 -0
  117. package/src/lib/db/schema/sqlite-auth.ts +112 -0
  118. package/src/lib/db/schema/sqlite-content.ts +20 -0
  119. package/src/lib/db/version-adapter.ts +248 -0
  120. package/src/lib/graphql/index.ts +1 -0
  121. package/src/lib/graphql/schema.ts +443 -0
  122. package/src/lib/i18n.tsx +353 -0
  123. package/src/lib/rate-limit.ts +267 -0
  124. package/src/lib/slugify.ts +15 -0
  125. package/src/lib/storage.ts +374 -0
  126. package/src/lib/store.ts +85 -0
  127. package/src/lib/validation.ts +250 -0
  128. package/src/middleware.ts +70 -11
  129. package/src/pages/[collection]/[id].astro +178 -122
  130. package/src/pages/[collection]/index.astro +24 -156
  131. package/src/pages/admin/api-explorer.astro +98 -0
  132. package/src/pages/admin/graphql-explorer.astro +40 -0
  133. package/src/pages/admin/graphql.astro +97 -0
  134. package/src/pages/admin/index.astro +200 -139
  135. package/src/pages/admin/keys.astro +8 -0
  136. package/src/pages/admin/rest-playground.astro +44 -0
  137. package/src/pages/admin/webhooks.astro +8 -0
  138. package/src/pages/api/[collection]/[id]/publish.ts +52 -0
  139. package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
  140. package/src/pages/api/[collection]/[id]/versions.ts +66 -0
  141. package/src/pages/api/[collection]/[id].ts +114 -159
  142. package/src/pages/api/[collection]/index.ts +150 -230
  143. package/src/pages/api/auth/[id].ts +48 -69
  144. package/src/pages/api/auth/audit-logs.ts +20 -43
  145. package/src/pages/api/auth/login.ts +159 -45
  146. package/src/pages/api/auth/logout.ts +42 -24
  147. package/src/pages/api/auth/refresh.ts +119 -0
  148. package/src/pages/api/auth/register.ts +110 -40
  149. package/src/pages/api/auth/users.ts +22 -97
  150. package/src/pages/api/collections.ts +59 -0
  151. package/src/pages/api/globals/[slug]/test.ts +172 -0
  152. package/src/pages/api/globals/[slug].ts +42 -0
  153. package/src/pages/api/graphql.ts +90 -0
  154. package/src/pages/api/health.ts +417 -40
  155. package/src/pages/api/keys/[id].ts +26 -0
  156. package/src/pages/api/keys/index.ts +75 -0
  157. package/src/pages/api/media/[id].ts +309 -0
  158. package/src/pages/api/media/folders.ts +609 -0
  159. package/src/pages/api/media/index.ts +146 -0
  160. package/src/pages/api/media/resize.ts +267 -0
  161. package/src/pages/api/search.ts +82 -0
  162. package/src/pages/api/slug-availability.ts +70 -0
  163. package/src/pages/api/storage-config.ts +20 -0
  164. package/src/pages/api/storage-status.ts +206 -0
  165. package/src/pages/api/upload.ts +334 -0
  166. package/src/pages/api/webhooks/index.ts +71 -0
  167. package/src/pages/audit/index.astro +2 -104
  168. package/src/pages/login.astro +11 -11
  169. package/src/pages/media.astro +10 -0
  170. package/src/pages/preview/[collection]/[id].astro +178 -0
  171. package/src/pages/register.astro +13 -13
  172. package/src/pages/roles/index.astro +21 -21
  173. package/src/pages/settings/[slug].astro +162 -0
  174. package/src/pages/settings/index.astro +9 -0
  175. package/src/pages/users/[id].astro +29 -21
  176. package/src/pages/users/index.astro +22 -17
  177. package/src/pages/users/new.astro +18 -17
  178. package/src/styles/main.css +563 -128
  179. package/src/components/layout/Sidebar.tsx +0 -497
@@ -1,210 +1,181 @@
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: "",
86
+ siteDescription: "",
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: "",
96
+ titleTemplate: "%s",
97
+ defaultDescription: "",
98
+ });
99
+ }
100
+ };
123
101
 
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
- }
102
+ initSeedData().catch(console.error);
135
103
 
136
- if (collection === "audit_logs") {
137
- const logs = await (adapter as any).redis.keys("kyro:auth:audit:*");
138
- const auditLogs: any[] = [];
104
+ const AUTH_COLLECTIONS = ["users", "roles", "audit_logs"];
139
105
 
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
- }
106
+ async function getAuthAdapterLocal() {
107
+ return await getAuthAdapter();
108
+ }
158
109
 
159
- const sortedLogs = auditLogs.sort(
160
- (a, b) =>
161
- new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
162
- );
110
+ export const GET: APIRoute = async ({ params, url }) => {
111
+ const collection = params.collection as string;
112
+ const page = parseInt(url.searchParams.get("page") || "1");
113
+ const limit = parseInt(url.searchParams.get("limit") || "25");
163
114
 
164
- const totalDocs = sortedLogs.length;
165
- const startIndex = (page - 1) * limit;
166
- const paginatedLogs = sortedLogs.slice(startIndex, startIndex + limit);
115
+ if (AUTH_COLLECTIONS.includes(collection)) {
116
+ const adapter = await getAuthAdapterLocal();
167
117
 
168
- await adapter.disconnect();
118
+ if (collection === "users") {
119
+ const users = await adapter.findAllUsers();
120
+ const safeUsers = users.map(({ passwordHash, ...user }) => user);
121
+ return new Response(
122
+ JSON.stringify({
123
+ docs: safeUsers,
124
+ totalDocs: safeUsers.length,
125
+ page,
126
+ limit,
127
+ totalPages: 1,
128
+ }),
129
+ { status: 200, headers: { "Content-Type": "application/json" } },
130
+ );
131
+ }
169
132
 
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
- }
133
+ if (collection === "roles") {
134
+ const roles = await adapter.findUserRoles();
135
+ return new Response(
136
+ JSON.stringify({
137
+ docs: roles,
138
+ totalDocs: roles.length,
139
+ page,
140
+ limit,
141
+ totalPages: 1,
142
+ }),
143
+ { status: 200, headers: { "Content-Type": "application/json" } },
144
+ );
145
+ }
181
146
 
182
- await adapter.disconnect();
183
- } catch (error) {
184
- console.error(`Error fetching ${collection}:`, error);
147
+ if (collection === "audit_logs") {
148
+ const offset = (page - 1) * limit;
149
+ const { logs, total } = await adapter.findAuditLogs({
150
+ limit,
151
+ offset,
152
+ });
185
153
  return new Response(
186
154
  JSON.stringify({
187
- error: `Failed to fetch ${collection}`,
188
- docs: [],
189
- totalDocs: 0,
155
+ docs: logs,
156
+ totalDocs: total,
157
+ page,
158
+ limit,
159
+ totalPages: Math.ceil(total / limit),
190
160
  }),
191
161
  { status: 200, headers: { "Content-Type": "application/json" } },
192
162
  );
193
163
  }
194
164
  }
195
165
 
196
- const page = parseInt(url.searchParams.get("page") || "1");
197
- const limit = parseInt(url.searchParams.get("limit") || "25");
198
-
199
166
  try {
200
- const result = dataStore.find(collection, { page, limit });
167
+ const result = await dataStore.find(collection, { page, limit });
201
168
  return new Response(JSON.stringify(result), {
202
169
  status: 200,
203
170
  headers: { "Content-Type": "application/json" },
204
171
  });
205
172
  } catch (error) {
206
173
  return new Response(
207
- JSON.stringify({ error: "Failed to fetch documents" }),
174
+ JSON.stringify({
175
+ error: "Failed to fetch documents",
176
+ docs: [],
177
+ totalDocs: 0,
178
+ }),
208
179
  { status: 500, headers: { "Content-Type": "application/json" } },
209
180
  );
210
181
  }
@@ -214,68 +185,17 @@ export const POST: APIRoute = async ({ params, request }) => {
214
185
  const collection = params.collection as string;
215
186
 
216
187
  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
- }
188
+ return new Response(
189
+ JSON.stringify({
190
+ error: `Collection ${collection} does not support POST`,
191
+ }),
192
+ { status: 405, headers: { "Content-Type": "application/json" } },
193
+ );
274
194
  }
275
195
 
276
196
  try {
277
197
  const body = await request.json();
278
- const doc = dataStore.create(collection, body);
198
+ const doc = await dataStore.create(collection, body);
279
199
  return new Response(JSON.stringify({ data: doc }), {
280
200
  status: 201,
281
201
  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,