@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,17 +1,14 @@
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
8
  const AUTH_COLLECTIONS = ["users", "roles", "audit_logs"];
8
9
 
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
- });
10
+ async function getAuthAdapterLocal() {
11
+ return await getAuthAdapter();
15
12
  }
16
13
 
17
14
  export const GET: APIRoute = async ({ params }) => {
@@ -19,76 +16,55 @@ export const GET: APIRoute = async ({ params }) => {
19
16
  const id = params.id as string;
20
17
 
21
18
  if (AUTH_COLLECTIONS.includes(collection)) {
22
- try {
23
- const adapter = await getAuthApi();
24
- await adapter.connect();
19
+ const adapter = await getAuthAdapterLocal();
25
20
 
26
- if (collection === "users") {
27
- const user = await adapter.findUserById(id);
28
- await adapter.disconnect();
29
-
30
- if (!user) {
31
- return new Response(JSON.stringify({ error: "User not found" }), {
32
- status: 404,
33
- headers: { "Content-Type": "application/json" },
34
- });
35
- }
36
-
37
- const { passwordHash, ...safeUser } = user;
38
- return new Response(JSON.stringify({ data: safeUser }), {
39
- status: 200,
21
+ if (collection === "users") {
22
+ const user = await adapter.findUserById(id);
23
+ if (!user) {
24
+ return new Response(JSON.stringify({ error: "User not found" }), {
25
+ status: 404,
40
26
  headers: { "Content-Type": "application/json" },
41
27
  });
42
28
  }
29
+ const { passwordHash, ...safeUser } = user;
30
+ return new Response(JSON.stringify({ data: safeUser }), {
31
+ status: 200,
32
+ headers: { "Content-Type": "application/json" },
33
+ });
34
+ }
43
35
 
44
- if (collection === "audit_logs") {
45
- const logData = await (adapter as any).redis.hgetall(
46
- `kyro:auth:audit:${id}`,
47
- );
48
- await adapter.disconnect();
49
-
50
- if (!logData || !logData.id) {
51
- return new Response(JSON.stringify({ error: "Log not found" }), {
52
- status: 404,
53
- headers: { "Content-Type": "application/json" },
54
- });
55
- }
56
-
57
- return new Response(
58
- JSON.stringify({
59
- data: {
60
- id: logData.id,
61
- action: logData.action,
62
- userId: logData.userId,
63
- userEmail: logData.userEmail,
64
- role: logData.role,
65
- resource: logData.resource,
66
- ipAddress: logData.ipAddress,
67
- success: logData.success === "true",
68
- error: logData.error,
69
- timestamp: logData.timestamp,
70
- },
71
- }),
72
- { status: 200, headers: { "Content-Type": "application/json" } },
73
- );
36
+ if (collection === "roles") {
37
+ const roles = await adapter.findUserRoles();
38
+ const role = roles.find((r) => r.id === id);
39
+ if (!role) {
40
+ return new Response(JSON.stringify({ error: "Role not found" }), {
41
+ status: 404,
42
+ headers: { "Content-Type": "application/json" },
43
+ });
74
44
  }
75
-
76
- await adapter.disconnect();
77
- } catch (error) {
78
- console.error(`Error fetching ${collection}:`, error);
79
- return new Response(
80
- JSON.stringify({ error: `Failed to fetch ${collection}` }),
81
- { status: 500, headers: { "Content-Type": "application/json" } },
82
- );
45
+ return new Response(JSON.stringify({ data: role }), {
46
+ status: 200,
47
+ headers: { "Content-Type": "application/json" },
48
+ });
83
49
  }
50
+
51
+ return new Response(
52
+ JSON.stringify({ error: `Collection ${collection} not supported` }),
53
+ { status: 404, headers: { "Content-Type": "application/json" } },
54
+ );
84
55
  }
85
56
 
86
57
  try {
87
- const doc = dataStore.findById(collection, id);
58
+ let doc = await dataStore.findById(collection, id);
59
+ if (!doc) {
60
+ const allDocs = (
61
+ await dataStore.find(collection, { page: 1, limit: 1000 })
62
+ ).docs;
63
+ doc = (allDocs || []).find((d: any) => d.slug === id) || null;
64
+ }
88
65
  if (!doc) {
89
66
  return new Response(JSON.stringify({ error: "Document not found" }), {
90
67
  status: 404,
91
- headers: { "Content-Type": "application/json" },
92
68
  });
93
69
  }
94
70
  return new Response(JSON.stringify({ data: doc }), {
@@ -98,7 +74,6 @@ export const GET: APIRoute = async ({ params }) => {
98
74
  } catch (error) {
99
75
  return new Response(JSON.stringify({ error: "Failed to fetch document" }), {
100
76
  status: 500,
101
- headers: { "Content-Type": "application/json" },
102
77
  });
103
78
  }
104
79
  };
@@ -108,77 +83,57 @@ export const PATCH: APIRoute = async ({ params, request }) => {
108
83
  const id = params.id as string;
109
84
 
110
85
  if (AUTH_COLLECTIONS.includes(collection)) {
111
- try {
112
- const adapter = await getAuthApi();
113
- await adapter.connect();
114
-
115
- const body = await request.json();
116
-
117
- if (collection === "users") {
118
- const existing = await adapter.findUserById(id);
119
- if (!existing) {
120
- await adapter.disconnect();
121
- return new Response(JSON.stringify({ error: "User not found" }), {
122
- status: 404,
123
- headers: { "Content-Type": "application/json" },
124
- });
125
- }
126
-
127
- const updateData: Record<string, unknown> = {};
128
- if (body.email !== undefined) updateData.email = body.email;
129
- if (body.role !== undefined) updateData.role = body.role;
130
- if (body.tenantId !== undefined) updateData.tenantId = body.tenantId;
131
- if (body.locked !== undefined) updateData.locked = body.locked;
132
- if (body.emailVerified !== undefined)
133
- updateData.emailVerified = body.emailVerified;
134
-
135
- if (body.password) {
136
- updateData.passwordHash = await adapter.hashPassword(body.password);
137
- }
138
-
139
- const user = await adapter.updateUser(id, updateData);
140
- await adapter.disconnect();
141
-
142
- if (!user) {
143
- return new Response(
144
- JSON.stringify({ error: "Failed to update user" }),
145
- {
146
- status: 500,
147
- headers: { "Content-Type": "application/json" },
148
- },
149
- );
150
- }
86
+ const adapter = await getAuthAdapterLocal();
87
+ const body = await request.json();
151
88
 
152
- const { passwordHash, ...safeUser } = user;
153
- return new Response(JSON.stringify({ data: safeUser }), {
154
- status: 200,
89
+ if (collection === "users") {
90
+ const existing = await adapter.findUserById(id);
91
+ if (!existing) {
92
+ return new Response(JSON.stringify({ error: "User not found" }), {
93
+ status: 404,
155
94
  headers: { "Content-Type": "application/json" },
156
95
  });
157
96
  }
158
97
 
159
- await adapter.disconnect();
160
- return new Response(
161
- JSON.stringify({
162
- error: `Collection ${collection} does not support PATCH`,
163
- }),
164
- { status: 405, headers: { "Content-Type": "application/json" } },
165
- );
166
- } catch (error) {
167
- console.error(`Error updating ${collection}:`, error);
168
- return new Response(
169
- JSON.stringify({ error: `Failed to update ${collection}` }),
170
- { status: 500, headers: { "Content-Type": "application/json" } },
171
- );
98
+ const updateData: Record<string, unknown> = {};
99
+ if (body.email !== undefined) updateData.email = body.email;
100
+ if (body.name !== undefined) updateData.name = body.name;
101
+ if (body.role !== undefined) updateData.role = body.role;
102
+ if (body.password) {
103
+ updateData.passwordHash = await adapter.hashPassword(body.password);
104
+ }
105
+
106
+ const user = await adapter.updateUser(id, updateData);
107
+ if (!user) {
108
+ return new Response(
109
+ JSON.stringify({ error: "Failed to update user" }),
110
+ {
111
+ status: 500,
112
+ },
113
+ );
114
+ }
115
+
116
+ const { passwordHash, ...safeUser } = user;
117
+ return new Response(JSON.stringify({ data: safeUser }), {
118
+ status: 200,
119
+ headers: { "Content-Type": "application/json" },
120
+ });
172
121
  }
122
+
123
+ return new Response(
124
+ JSON.stringify({
125
+ error: `Collection ${collection} does not support PATCH`,
126
+ }),
127
+ { status: 405, headers: { "Content-Type": "application/json" } },
128
+ );
173
129
  }
174
130
 
175
131
  try {
176
132
  const body = await request.json();
177
- const doc = dataStore.update(collection, id, body);
133
+ const doc = await dataStore.update(collection, id, body);
178
134
  if (!doc) {
179
135
  return new Response(JSON.stringify({ error: "Document not found" }), {
180
136
  status: 404,
181
- headers: { "Content-Type": "application/json" },
182
137
  });
183
138
  }
184
139
  return new Response(JSON.stringify({ data: doc }), {
@@ -188,7 +143,9 @@ export const PATCH: APIRoute = async ({ params, request }) => {
188
143
  } catch (error) {
189
144
  return new Response(
190
145
  JSON.stringify({ error: "Failed to update document" }),
191
- { status: 500, headers: { "Content-Type": "application/json" } },
146
+ {
147
+ status: 500,
148
+ },
192
149
  );
193
150
  }
194
151
  };
@@ -198,51 +155,35 @@ export const DELETE: APIRoute = async ({ params }) => {
198
155
  const id = params.id as string;
199
156
 
200
157
  if (AUTH_COLLECTIONS.includes(collection)) {
201
- try {
202
- const adapter = await getAuthApi();
203
- await adapter.connect();
204
-
205
- if (collection === "users") {
206
- const existing = await adapter.findUserById(id);
207
- if (!existing) {
208
- await adapter.disconnect();
209
- return new Response(JSON.stringify({ error: "User not found" }), {
210
- status: 404,
211
- headers: { "Content-Type": "application/json" },
212
- });
213
- }
158
+ const adapter = await getAuthAdapterLocal();
214
159
 
215
- await adapter.deleteUser(id);
216
- await adapter.disconnect();
217
-
218
- return new Response(JSON.stringify({ success: true }), {
219
- status: 200,
220
- headers: { "Content-Type": "application/json" },
160
+ if (collection === "users") {
161
+ const existing = await adapter.findUserById(id);
162
+ if (!existing) {
163
+ return new Response(JSON.stringify({ error: "User not found" }), {
164
+ status: 404,
221
165
  });
222
166
  }
223
-
224
- await adapter.disconnect();
225
- return new Response(
226
- JSON.stringify({
227
- error: `Collection ${collection} does not support DELETE`,
228
- }),
229
- { status: 405, headers: { "Content-Type": "application/json" } },
230
- );
231
- } catch (error) {
232
- console.error(`Error deleting ${collection}:`, error);
233
- return new Response(
234
- JSON.stringify({ error: `Failed to delete ${collection}` }),
235
- { status: 500, headers: { "Content-Type": "application/json" } },
236
- );
167
+ await adapter.deleteUser(id);
168
+ return new Response(JSON.stringify({ success: true }), {
169
+ status: 200,
170
+ headers: { "Content-Type": "application/json" },
171
+ });
237
172
  }
173
+
174
+ return new Response(
175
+ JSON.stringify({
176
+ error: `Collection ${collection} does not support DELETE`,
177
+ }),
178
+ { status: 405, headers: { "Content-Type": "application/json" } },
179
+ );
238
180
  }
239
181
 
240
182
  try {
241
- const success = dataStore.delete(collection, id);
183
+ const success = await dataStore.delete(collection, id);
242
184
  if (!success) {
243
185
  return new Response(JSON.stringify({ error: "Document not found" }), {
244
186
  status: 404,
245
- headers: { "Content-Type": "application/json" },
246
187
  });
247
188
  }
248
189
  return new Response(JSON.stringify({ success: true }), {
@@ -252,7 +193,9 @@ export const DELETE: APIRoute = async ({ params }) => {
252
193
  } catch (error) {
253
194
  return new Response(
254
195
  JSON.stringify({ error: "Failed to delete document" }),
255
- { status: 500, headers: { "Content-Type": "application/json" } },
196
+ {
197
+ status: 500,
198
+ },
256
199
  );
257
200
  }
258
201
  };