@kyro-cms/admin 0.1.6 → 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.
- package/README.md +149 -51
- package/package.json +53 -6
- package/src/collections/auth/index.ts +2 -2
- package/src/collections/portfolio/index.ts +343 -0
- package/src/components/ActionBar.tsx +153 -16
- package/src/components/Admin.tsx +136 -27
- package/src/components/ApiExplorer.tsx +325 -0
- package/src/components/ApiKeysManager.tsx +563 -0
- package/src/components/AuditLogsPage.tsx +664 -0
- package/src/components/AutoForm.tsx +1417 -661
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +3 -3
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +199 -57
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +786 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +191 -53
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +149 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- package/src/components/UserManagement.tsx +204 -0
- package/src/components/VersionHistoryPanel.tsx +3 -3
- package/src/components/WebhookManager.tsx +608 -0
- package/src/components/blocks/AccordionBlock.tsx +97 -0
- package/src/components/blocks/ArrayBlock.tsx +75 -0
- package/src/components/blocks/BlockEditModal.MARKER +12 -0
- package/src/components/blocks/BlockEditModal.tsx +774 -0
- package/src/components/blocks/ButtonBlock.tsx +165 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +66 -0
- package/src/components/blocks/ColumnsBlock.tsx +151 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +64 -0
- package/src/components/blocks/HeadingBlock.tsx +81 -0
- package/src/components/blocks/HeroBlock.tsx +157 -0
- package/src/components/blocks/ImageBlock.tsx +83 -0
- package/src/components/blocks/LinkBlock.tsx +71 -0
- package/src/components/blocks/ListBlock.tsx +39 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +279 -0
- package/src/components/blocks/VStackBlock.tsx +75 -0
- package/src/components/blocks/VideoBlock.tsx +45 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/CheckboxField.tsx +15 -9
- package/src/components/fields/CodeField.tsx +234 -0
- package/src/components/fields/DateField.tsx +38 -11
- package/src/components/fields/EditorClient.tsx +271 -0
- package/src/components/fields/FileField.tsx +390 -0
- package/src/components/fields/HybridContentField.tsx +109 -0
- package/src/components/fields/ImageField.tsx +429 -0
- package/src/components/fields/JSONField.tsx +361 -0
- package/src/components/fields/MarkdownField.tsx +282 -0
- package/src/components/fields/NumberField.tsx +42 -12
- package/src/components/fields/PortableTextField.tsx +143 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipField.tsx +231 -59
- package/src/components/fields/SelectField.tsx +25 -15
- package/src/components/fields/TextField.tsx +45 -14
- package/src/components/fields/extensions/blockComponents.tsx +237 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +13 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +2 -2
- package/src/components/ui/Badge.tsx +9 -4
- package/src/components/ui/BlockDrawer.tsx +79 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/CommandPalette.tsx +362 -0
- package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
- package/src/components/ui/Dropdown.tsx +1 -1
- package/src/components/ui/Modal.tsx +37 -12
- package/src/components/ui/PromptModal.tsx +94 -0
- package/src/components/ui/SlidePanel.tsx +43 -16
- package/src/components/ui/Toast.tsx +80 -14
- package/src/env.d.ts +16 -0
- package/src/env.ts +20 -0
- package/src/index.ts +0 -1
- package/src/layouts/AdminLayout.astro +164 -170
- package/src/layouts/AuthLayout.astro +23 -6
- package/src/lib/MediaService.ts +541 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +22 -6
- package/src/lib/dataStore.ts +132 -74
- package/src/lib/db/adapter.ts +54 -0
- package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
- package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
- package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
- package/src/lib/db/index.ts +449 -0
- package/src/lib/db/mongodb-adapter.ts +207 -0
- package/src/lib/db/mongodb-auth-adapter.ts +305 -0
- package/src/lib/db/schema/mysql-auth.ts +113 -0
- package/src/lib/db/schema/mysql-content.ts +20 -0
- package/src/lib/db/schema/postgres-auth.ts +116 -0
- package/src/lib/db/schema/postgres-content.ts +35 -0
- package/src/lib/db/schema/postgres-media.ts +52 -0
- package/src/lib/db/schema/postgres-settings.ts +11 -0
- package/src/lib/db/schema/sqlite-auth.ts +112 -0
- package/src/lib/db/schema/sqlite-content.ts +20 -0
- package/src/lib/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/middleware.ts +70 -11
- package/src/pages/[collection]/[id].astro +178 -122
- package/src/pages/[collection]/index.astro +24 -156
- package/src/pages/admin/api-explorer.astro +98 -0
- package/src/pages/admin/graphql-explorer.astro +40 -0
- package/src/pages/admin/graphql.astro +97 -0
- package/src/pages/admin/index.astro +200 -139
- package/src/pages/admin/keys.astro +8 -0
- package/src/pages/admin/rest-playground.astro +44 -0
- package/src/pages/admin/webhooks.astro +8 -0
- package/src/pages/api/[collection]/[id]/publish.ts +44 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +36 -0
- package/src/pages/api/[collection]/[id].ts +102 -159
- package/src/pages/api/[collection]/index.ts +151 -230
- package/src/pages/api/auth/[id].ts +48 -69
- package/src/pages/api/auth/audit-logs.ts +20 -43
- package/src/pages/api/auth/login.ts +159 -45
- package/src/pages/api/auth/logout.ts +42 -24
- package/src/pages/api/auth/refresh.ts +119 -0
- package/src/pages/api/auth/register.ts +110 -40
- package/src/pages/api/auth/users.ts +22 -97
- package/src/pages/api/collections.ts +59 -0
- package/src/pages/api/globals/[slug]/test.ts +172 -0
- package/src/pages/api/globals/[slug].ts +42 -0
- package/src/pages/api/graphql.ts +90 -0
- package/src/pages/api/health.ts +417 -40
- package/src/pages/api/keys/[id].ts +26 -0
- package/src/pages/api/keys/index.ts +75 -0
- package/src/pages/api/media/[id].ts +309 -0
- package/src/pages/api/media/folders.ts +609 -0
- package/src/pages/api/media/index.ts +146 -0
- package/src/pages/api/media/resize.ts +267 -0
- package/src/pages/api/search.ts +82 -0
- package/src/pages/api/slug-availability.ts +70 -0
- package/src/pages/api/storage-config.ts +20 -0
- package/src/pages/api/storage-status.ts +206 -0
- package/src/pages/api/upload.ts +334 -0
- package/src/pages/api/webhooks/index.ts +71 -0
- package/src/pages/audit/index.astro +2 -104
- package/src/pages/login.astro +11 -11
- package/src/pages/media.astro +10 -0
- package/src/pages/preview/[collection]/[id].astro +178 -0
- package/src/pages/register.astro +13 -13
- package/src/pages/roles/index.astro +21 -21
- package/src/pages/settings/[slug].astro +162 -0
- package/src/pages/settings/index.astro +9 -0
- package/src/pages/users/[id].astro +29 -21
- package/src/pages/users/index.astro +22 -17
- package/src/pages/users/new.astro +18 -17
- package/src/styles/main.css +553 -128
- package/src/components/layout/Sidebar.tsx +0 -497
|
@@ -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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
const paginatedLogs = sortedLogs.slice(startIndex, startIndex + limit);
|
|
116
|
+
if (AUTH_COLLECTIONS.includes(collection)) {
|
|
117
|
+
const adapter = await getAuthAdapterLocal();
|
|
167
118
|
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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({
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
33
|
+
const adapter = await getAuthAdapter();
|
|
55
34
|
|
|
56
35
|
try {
|
|
57
36
|
const body = await request.json();
|
|
58
|
-
const { email, role
|
|
37
|
+
const { email, role } = body;
|
|
59
38
|
|
|
60
|
-
const
|
|
61
|
-
|
|
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
|
-
|
|
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
|
|
104
|
-
await ensureConnection();
|
|
105
|
-
|
|
64
|
+
export const PATCH: APIRoute = async ({ params, request }) => {
|
|
106
65
|
const { id } = params;
|
|
107
|
-
const
|
|
66
|
+
const adapter = await getAuthAdapter();
|
|
108
67
|
|
|
109
68
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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,
|