@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.
- package/README.md +149 -51
- package/package.json +54 -5
- 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 +137 -28
- 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 +2155 -770
- package/src/components/BrandingHub.tsx +267 -0
- package/src/components/BulkActionsBar.tsx +3 -3
- package/src/components/CreateView.tsx +4 -4
- package/src/components/Dashboard.tsx +393 -0
- package/src/components/DetailView.tsx +200 -58
- package/src/components/DeveloperCenter.tsx +403 -0
- package/src/components/EnhancedListView.tsx +890 -0
- package/src/components/GraphQLExplorer.tsx +675 -0
- package/src/components/GraphQLPlayground.tsx +627 -0
- package/src/components/ListView.tsx +192 -54
- package/src/components/MediaGallery.tsx +1569 -0
- package/src/components/Modal.tsx +206 -0
- package/src/components/RestPlayground.tsx +951 -0
- package/src/components/Sidebar.astro +237 -0
- package/src/components/ThemeProvider.tsx +8 -2
- 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 +65 -0
- package/src/components/blocks/ArrayBlock.tsx +84 -0
- package/src/components/blocks/BlockEditModal.tsx +363 -0
- package/src/components/blocks/ButtonBlock.tsx +64 -0
- package/src/components/blocks/ChildBlocksTree.tsx +551 -0
- package/src/components/blocks/CodeBlock.tsx +114 -0
- package/src/components/blocks/ColumnsBlock.tsx +93 -0
- package/src/components/blocks/DividerBlock.tsx +43 -0
- package/src/components/blocks/FileBlock.tsx +63 -0
- package/src/components/blocks/HeadingBlock.tsx +59 -0
- package/src/components/blocks/HeroBlock.tsx +99 -0
- package/src/components/blocks/ImageBlock.tsx +82 -0
- package/src/components/blocks/LinkBlock.tsx +65 -0
- package/src/components/blocks/ListBlock.tsx +60 -0
- package/src/components/blocks/ParagraphBlock.tsx +61 -0
- package/src/components/blocks/RelationshipBlock.tsx +72 -0
- package/src/components/blocks/RichTextBlock.tsx +66 -0
- package/src/components/blocks/VStackBlock.tsx +61 -0
- package/src/components/blocks/VideoBlock.tsx +65 -0
- package/src/components/blocks/index.ts +10 -0
- package/src/components/fields/AccordionField.tsx +213 -0
- package/src/components/fields/ArrayField.tsx +241 -0
- package/src/components/fields/BlocksField.tsx +323 -0
- package/src/components/fields/ButtonField.tsx +53 -0
- package/src/components/fields/CheckboxField.tsx +18 -8
- package/src/components/fields/ChildrenField.tsx +48 -0
- package/src/components/fields/CodeField.tsx +294 -0
- package/src/components/fields/ColumnsField.tsx +137 -0
- package/src/components/fields/DateField.tsx +24 -12
- package/src/components/fields/EditorClient.tsx +537 -0
- package/src/components/fields/HeadingField.tsx +31 -0
- package/src/components/fields/HeroField.tsx +101 -0
- package/src/components/fields/JSONField.tsx +341 -0
- package/src/components/fields/LinkField.tsx +81 -0
- package/src/components/fields/ListField.tsx +74 -0
- package/src/components/fields/MarkdownField.tsx +260 -0
- package/src/components/fields/NumberField.tsx +25 -13
- package/src/components/fields/PortableTextField.tsx +155 -0
- package/src/components/fields/PortableTextRenderer.tsx +68 -0
- package/src/components/fields/RelationshipBlockField.tsx +233 -0
- package/src/components/fields/RelationshipField.tsx +278 -60
- package/src/components/fields/SelectField.tsx +28 -16
- package/src/components/fields/TextField.tsx +31 -15
- package/src/components/fields/UploadField.tsx +613 -0
- package/src/components/fields/VideoField.tsx +73 -0
- package/src/components/fields/extensions/blockComponents.tsx +247 -0
- package/src/components/fields/extensions/blocksStore.ts +273 -0
- package/src/components/fields/index.ts +24 -0
- package/src/components/index.ts +1 -2
- package/src/components/layout/Header.tsx +2 -2
- package/src/components/layout/Layout.tsx +3 -3
- 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/api.ts +163 -0
- package/src/lib/auth/sqlite-adapter.ts +319 -0
- package/src/lib/config.ts +23 -7
- package/src/lib/dataStore.ts +188 -73
- package/src/lib/date-utils.ts +69 -0
- 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/db/version-adapter.ts +248 -0
- package/src/lib/graphql/index.ts +1 -0
- package/src/lib/graphql/schema.ts +443 -0
- package/src/lib/i18n.tsx +353 -0
- package/src/lib/rate-limit.ts +267 -0
- package/src/lib/slugify.ts +15 -0
- package/src/lib/storage.ts +374 -0
- package/src/lib/store.ts +85 -0
- package/src/lib/validation.ts +250 -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 +52 -0
- package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
- package/src/pages/api/[collection]/[id]/versions.ts +66 -0
- package/src/pages/api/[collection]/[id].ts +114 -159
- package/src/pages/api/[collection]/index.ts +150 -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 +563 -128
- 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
|
|
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: "",
|
|
86
|
+
siteDescription: "",
|
|
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: "",
|
|
96
|
+
titleTemplate: "%s",
|
|
97
|
+
defaultDescription: "",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
123
101
|
|
|
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
|
-
}
|
|
102
|
+
initSeedData().catch(console.error);
|
|
135
103
|
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
106
|
+
async function getAuthAdapterLocal() {
|
|
107
|
+
return await getAuthAdapter();
|
|
108
|
+
}
|
|
158
109
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
const paginatedLogs = sortedLogs.slice(startIndex, startIndex + limit);
|
|
115
|
+
if (AUTH_COLLECTIONS.includes(collection)) {
|
|
116
|
+
const adapter = await getAuthAdapterLocal();
|
|
167
117
|
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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({
|
|
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
|
-
|
|
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
|
-
}
|
|
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 {
|
|
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,
|