@kyro-cms/admin 0.3.1 → 0.3.4
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/dist/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
-
import type { AuthAdapter, AuthUser, Session, UserRole } from "@kyro-cms/core";
|
|
3
|
-
import { eq, and, gt, desc, sql } from "drizzle-orm";
|
|
4
|
-
import bcrypt from "bcryptjs";
|
|
5
|
-
import { randomBytes } from "crypto";
|
|
6
|
-
import postgres from "postgres";
|
|
7
|
-
import { drizzle } from "drizzle-orm/postgres-js";
|
|
8
|
-
import {
|
|
9
|
-
users,
|
|
10
|
-
sessions,
|
|
11
|
-
roles,
|
|
12
|
-
auditLogs,
|
|
13
|
-
passwordHistory,
|
|
14
|
-
lockouts,
|
|
15
|
-
} from "./schema/postgres-auth";
|
|
16
|
-
import type { AuditLog, AuditLogFilter } from "@kyro-cms/core";
|
|
17
|
-
|
|
18
|
-
export interface PostgresAuthAdapterOptions {
|
|
19
|
-
db?: PostgresJsDatabase;
|
|
20
|
-
connectionString?: string;
|
|
21
|
-
sessionTTL?: number;
|
|
22
|
-
refreshTokenTTL?: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DEFAULT_SESSION_TTL = 86400;
|
|
26
|
-
const DEFAULT_REFRESH_TTL = 604800;
|
|
27
|
-
|
|
28
|
-
export class PostgresAuthAdapter implements AuthAdapter {
|
|
29
|
-
private db: PostgresJsDatabase;
|
|
30
|
-
private sessionTTL: number;
|
|
31
|
-
private refreshTTL: number;
|
|
32
|
-
|
|
33
|
-
constructor(private options: PostgresAuthAdapterOptions) {
|
|
34
|
-
if (options.db) {
|
|
35
|
-
this.db = options.db;
|
|
36
|
-
} else if (options.connectionString) {
|
|
37
|
-
const client = postgres(options.connectionString, { max: 10 });
|
|
38
|
-
this.db = drizzle(client);
|
|
39
|
-
} else {
|
|
40
|
-
throw new Error("Either db or connectionString must be provided");
|
|
41
|
-
}
|
|
42
|
-
this.sessionTTL = options.sessionTTL || DEFAULT_SESSION_TTL;
|
|
43
|
-
this.refreshTTL = options.refreshTokenTTL || DEFAULT_REFRESH_TTL;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private generateId(): string {
|
|
47
|
-
return crypto.randomUUID();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async createUser(data: {
|
|
51
|
-
email: string;
|
|
52
|
-
passwordHash: string;
|
|
53
|
-
role: UserRole;
|
|
54
|
-
tenantId?: string;
|
|
55
|
-
}): Promise<AuthUser> {
|
|
56
|
-
const now = new Date();
|
|
57
|
-
const [user] = await this.db
|
|
58
|
-
.insert(users)
|
|
59
|
-
.values({
|
|
60
|
-
email: data.email.toLowerCase(),
|
|
61
|
-
passwordHash: data.passwordHash,
|
|
62
|
-
role: data.role,
|
|
63
|
-
tenantId: data.tenantId,
|
|
64
|
-
})
|
|
65
|
-
.returning();
|
|
66
|
-
return {
|
|
67
|
-
id: user.id,
|
|
68
|
-
email: user.email,
|
|
69
|
-
role: user.role as UserRole,
|
|
70
|
-
tenantId: user.tenantId || undefined,
|
|
71
|
-
createdAt: user.createdAt.toISOString(),
|
|
72
|
-
updatedAt: user.updatedAt.toISOString(),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async findUserByEmail(email: string): Promise<AuthUser | null> {
|
|
77
|
-
const result = await this.db
|
|
78
|
-
.select()
|
|
79
|
-
.from(users)
|
|
80
|
-
.where(eq(users.email, email.toLowerCase()))
|
|
81
|
-
.limit(1);
|
|
82
|
-
const user = result[0];
|
|
83
|
-
if (!user) return null;
|
|
84
|
-
return {
|
|
85
|
-
id: user.id,
|
|
86
|
-
email: user.email,
|
|
87
|
-
role: user.role as UserRole,
|
|
88
|
-
tenantId: user.tenantId || undefined,
|
|
89
|
-
createdAt: user.createdAt.toISOString(),
|
|
90
|
-
updatedAt: user.updatedAt.toISOString(),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async findUserById(id: string): Promise<AuthUser | null> {
|
|
95
|
-
const result = await this.db
|
|
96
|
-
.select()
|
|
97
|
-
.from(users)
|
|
98
|
-
.where(eq(users.id, id))
|
|
99
|
-
.limit(1);
|
|
100
|
-
const user = result[0];
|
|
101
|
-
if (!user) return null;
|
|
102
|
-
return {
|
|
103
|
-
id: user.id,
|
|
104
|
-
email: user.email,
|
|
105
|
-
role: user.role as UserRole,
|
|
106
|
-
tenantId: user.tenantId || undefined,
|
|
107
|
-
createdAt: user.createdAt.toISOString(),
|
|
108
|
-
updatedAt: user.updatedAt.toISOString(),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async updateUser(
|
|
113
|
-
id: string,
|
|
114
|
-
data: Partial<AuthUser>,
|
|
115
|
-
): Promise<AuthUser | null> {
|
|
116
|
-
const updates: Record<string, unknown> = { updatedAt: new Date() };
|
|
117
|
-
if (data.email) updates.email = data.email.toLowerCase();
|
|
118
|
-
if (data.role) updates.role = data.role;
|
|
119
|
-
if (data.passwordHash) updates.passwordHash = data.passwordHash;
|
|
120
|
-
|
|
121
|
-
const result = await this.db
|
|
122
|
-
.update(users)
|
|
123
|
-
.set(updates)
|
|
124
|
-
.where(eq(users.id, id))
|
|
125
|
-
.returning();
|
|
126
|
-
const user = result[0];
|
|
127
|
-
if (!user) return null;
|
|
128
|
-
return {
|
|
129
|
-
id: user.id,
|
|
130
|
-
email: user.email,
|
|
131
|
-
role: user.role as UserRole,
|
|
132
|
-
tenantId: user.tenantId || undefined,
|
|
133
|
-
createdAt: user.createdAt.toISOString(),
|
|
134
|
-
updatedAt: user.updatedAt.toISOString(),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async deleteUser(id: string): Promise<boolean> {
|
|
139
|
-
await this.db.delete(users).where(eq(users.id, id));
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
144
|
-
return bcrypt.compare(password, hash);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async hashPassword(password: string): Promise<string> {
|
|
148
|
-
return bcrypt.hash(password, 10);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async createSession(
|
|
152
|
-
userId: string,
|
|
153
|
-
data?: { ipAddress?: string; userAgent?: string },
|
|
154
|
-
): Promise<Session> {
|
|
155
|
-
const now = new Date();
|
|
156
|
-
const expiresAt = new Date(now.getTime() + this.sessionTTL * 1000);
|
|
157
|
-
const token = randomBytes(32).toString("hex");
|
|
158
|
-
const refreshToken = randomBytes(32).toString("hex");
|
|
159
|
-
|
|
160
|
-
const result = await this.db
|
|
161
|
-
.insert(sessions)
|
|
162
|
-
.values({
|
|
163
|
-
userId,
|
|
164
|
-
token,
|
|
165
|
-
refreshToken,
|
|
166
|
-
expiresAt,
|
|
167
|
-
})
|
|
168
|
-
.returning();
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
id: result[0].id,
|
|
172
|
-
token: result[0].token,
|
|
173
|
-
refreshToken: result[0].refreshToken || undefined,
|
|
174
|
-
userId: result[0].userId,
|
|
175
|
-
expiresAt: result[0].expiresAt.toISOString(),
|
|
176
|
-
createdAt: now.toISOString(),
|
|
177
|
-
ipAddress: data?.ipAddress,
|
|
178
|
-
userAgent: data?.userAgent,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async findSessionByToken(token: string): Promise<Session | null> {
|
|
183
|
-
const now = new Date();
|
|
184
|
-
const result = await this.db
|
|
185
|
-
.select()
|
|
186
|
-
.from(sessions)
|
|
187
|
-
.where(and(eq(sessions.token, token), gt(sessions.expiresAt, now)))
|
|
188
|
-
.limit(1);
|
|
189
|
-
const session = result[0];
|
|
190
|
-
if (!session) return null;
|
|
191
|
-
return {
|
|
192
|
-
id: session.id,
|
|
193
|
-
token: session.token,
|
|
194
|
-
refreshToken: session.refreshToken || undefined,
|
|
195
|
-
userId: session.userId,
|
|
196
|
-
expiresAt: session.expiresAt.toISOString(),
|
|
197
|
-
createdAt: session.createdAt.toISOString(),
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async deleteSession(sessionId: string): Promise<boolean> {
|
|
202
|
-
await this.db.delete(sessions).where(eq(sessions.token, sessionId));
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async deleteUserSessions(userId: string): Promise<number> {
|
|
207
|
-
await this.db.delete(sessions).where(eq(sessions.userId, userId));
|
|
208
|
-
return 1;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async hasAnyUsers(): Promise<boolean> {
|
|
212
|
-
const result = await this.db
|
|
213
|
-
.select({ count: sql<number>`count(*)` })
|
|
214
|
-
.from(users);
|
|
215
|
-
return Number(result[0]?.count || 0) > 0;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async findAuditLogs(
|
|
219
|
-
filter: AuditLogFilter,
|
|
220
|
-
): Promise<{ logs: AuditLog[]; total: number }> {
|
|
221
|
-
const {
|
|
222
|
-
limit = 50,
|
|
223
|
-
offset = 0,
|
|
224
|
-
userId,
|
|
225
|
-
action,
|
|
226
|
-
success,
|
|
227
|
-
startDate,
|
|
228
|
-
endDate,
|
|
229
|
-
} = filter;
|
|
230
|
-
|
|
231
|
-
const conditions = [];
|
|
232
|
-
if (userId) conditions.push(eq(auditLogs.userId, userId));
|
|
233
|
-
if (action) {
|
|
234
|
-
if (Array.isArray(action)) {
|
|
235
|
-
conditions.push(sql`${auditLogs.action} = ANY(${action})`);
|
|
236
|
-
} else {
|
|
237
|
-
conditions.push(eq(auditLogs.action, action));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (success !== undefined) conditions.push(eq(auditLogs.success, success));
|
|
241
|
-
if (startDate) conditions.push(sql`${auditLogs.createdAt} >= ${startDate}`);
|
|
242
|
-
if (endDate) conditions.push(sql`${auditLogs.createdAt} <= ${endDate}`);
|
|
243
|
-
|
|
244
|
-
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
245
|
-
|
|
246
|
-
const countResult = await this.db
|
|
247
|
-
.select({ count: sql<number>`count(*)` })
|
|
248
|
-
.from(auditLogs)
|
|
249
|
-
.where(whereClause);
|
|
250
|
-
|
|
251
|
-
const logs = await this.db
|
|
252
|
-
.select()
|
|
253
|
-
.from(auditLogs)
|
|
254
|
-
.where(whereClause)
|
|
255
|
-
.orderBy(desc(auditLogs.createdAt))
|
|
256
|
-
.limit(limit)
|
|
257
|
-
.offset(offset);
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
logs: logs.map((log) => ({
|
|
261
|
-
id: log.id,
|
|
262
|
-
timestamp: log.createdAt,
|
|
263
|
-
action: log.action as AuditLog["action"],
|
|
264
|
-
userId: log.userId || undefined,
|
|
265
|
-
userEmail: log.userEmail || undefined,
|
|
266
|
-
role: log.role || undefined,
|
|
267
|
-
resource: log.resource,
|
|
268
|
-
ipAddress: log.ipAddress || undefined,
|
|
269
|
-
userAgent: log.userAgent || undefined,
|
|
270
|
-
success: log.success,
|
|
271
|
-
error: log.error || undefined,
|
|
272
|
-
metadata: log.metadata || undefined,
|
|
273
|
-
})),
|
|
274
|
-
total: Number(countResult[0]?.count || 0),
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async createAuditLog(
|
|
279
|
-
data: Omit<AuditLog, "id" | "timestamp">,
|
|
280
|
-
): Promise<AuditLog> {
|
|
281
|
-
const id = this.generateId();
|
|
282
|
-
const timestamp = new Date();
|
|
283
|
-
|
|
284
|
-
await this.db.insert(auditLogs).values({
|
|
285
|
-
id,
|
|
286
|
-
action: data.action,
|
|
287
|
-
userId: data.userId,
|
|
288
|
-
userEmail: data.userEmail,
|
|
289
|
-
role: data.role,
|
|
290
|
-
resource: data.resource,
|
|
291
|
-
ipAddress: data.ipAddress,
|
|
292
|
-
userAgent: data.userAgent,
|
|
293
|
-
success: data.success,
|
|
294
|
-
error: data.error,
|
|
295
|
-
metadata: data.metadata,
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
...data,
|
|
300
|
-
id,
|
|
301
|
-
timestamp,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
}
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import type { CollectionConfig } from "@kyro-cms/core";
|
|
2
|
-
import type { DatabaseAdapter, DatabaseConfig } from "./adapter";
|
|
3
|
-
import Database from "better-sqlite3";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import { randomBytes } from "crypto";
|
|
7
|
-
import { eq, desc, sql, and } from "drizzle-orm";
|
|
8
|
-
import * as contentSchema from "./schema/sqlite-content";
|
|
9
|
-
|
|
10
|
-
function getContentDbPath(): string {
|
|
11
|
-
return (
|
|
12
|
-
process.env.CONTENT_DB_PATH ||
|
|
13
|
-
path.join(process.cwd(), "data", "content.db")
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function ensureDbDir(dbPath: string): void {
|
|
18
|
-
const dir = path.dirname(dbPath);
|
|
19
|
-
if (!fs.existsSync(dir)) {
|
|
20
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class DrizzleSQLiteAdapter implements DatabaseAdapter {
|
|
25
|
-
private db: Database.Database;
|
|
26
|
-
private initialized = false;
|
|
27
|
-
|
|
28
|
-
constructor(config?: DatabaseConfig) {
|
|
29
|
-
const dbPath = config?.contentDbPath || getContentDbPath();
|
|
30
|
-
ensureDbDir(dbPath);
|
|
31
|
-
this.db = new Database(dbPath);
|
|
32
|
-
this.db.pragma("journal_mode = WAL");
|
|
33
|
-
this.db.pragma("synchronous = NORMAL");
|
|
34
|
-
this.db.pragma("cache_size = -64000");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
initialize(collections: Record<string, CollectionConfig>) {
|
|
38
|
-
if (this.initialized) return;
|
|
39
|
-
|
|
40
|
-
this.db.exec(`
|
|
41
|
-
CREATE TABLE IF NOT EXISTS documents (
|
|
42
|
-
id TEXT PRIMARY KEY,
|
|
43
|
-
collection TEXT NOT NULL,
|
|
44
|
-
data TEXT NOT NULL DEFAULT '{}',
|
|
45
|
-
created_at TEXT NOT NULL,
|
|
46
|
-
updated_at TEXT NOT NULL
|
|
47
|
-
);
|
|
48
|
-
CREATE TABLE IF NOT EXISTS globals (
|
|
49
|
-
slug TEXT PRIMARY KEY,
|
|
50
|
-
data TEXT NOT NULL DEFAULT '{}',
|
|
51
|
-
updated_at TEXT NOT NULL
|
|
52
|
-
);
|
|
53
|
-
CREATE INDEX IF NOT EXISTS idx_documents_collection ON documents(collection);
|
|
54
|
-
CREATE INDEX IF NOT EXISTS idx_documents_created_at ON documents(created_at);
|
|
55
|
-
`);
|
|
56
|
-
|
|
57
|
-
this.initialized = true;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private getTimestamp(): string {
|
|
61
|
-
return new Date().toISOString();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private generateId(): string {
|
|
65
|
-
return randomBytes(16).toString("hex");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async find<T = any>(
|
|
69
|
-
slug: string,
|
|
70
|
-
options: { page?: number; limit?: number } = {},
|
|
71
|
-
): Promise<{
|
|
72
|
-
docs: T[];
|
|
73
|
-
totalDocs: number;
|
|
74
|
-
totalPages: number;
|
|
75
|
-
page: number;
|
|
76
|
-
}> {
|
|
77
|
-
const page = options.page || 1;
|
|
78
|
-
const limit = options.limit || 25;
|
|
79
|
-
const start = (page - 1) * limit;
|
|
80
|
-
|
|
81
|
-
const totalResult = this.db
|
|
82
|
-
.prepare("SELECT COUNT(*) as cnt FROM documents WHERE collection = ?")
|
|
83
|
-
.get(slug) as { cnt: number };
|
|
84
|
-
const count = totalResult?.cnt || 0;
|
|
85
|
-
|
|
86
|
-
const docs = this.db
|
|
87
|
-
.prepare(
|
|
88
|
-
"SELECT data FROM documents WHERE collection = ? ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
|
89
|
-
)
|
|
90
|
-
.all(slug, limit, start) as { data: string }[];
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
docs: docs.map((d) => JSON.parse(d.data) as T),
|
|
94
|
-
totalDocs: count,
|
|
95
|
-
totalPages: Math.ceil(count / limit),
|
|
96
|
-
page,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async findById<T = any>(slug: string, id: string): Promise<T | null> {
|
|
101
|
-
const doc = this.db
|
|
102
|
-
.prepare("SELECT data FROM documents WHERE collection = ? AND id = ?")
|
|
103
|
-
.get(slug, id) as { data: string } | undefined;
|
|
104
|
-
return doc ? (JSON.parse(doc.data) as T) : null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async create<T = any>(slug: string, data: Partial<T>): Promise<T> {
|
|
108
|
-
const now = this.getTimestamp();
|
|
109
|
-
const id = (data as any)?.id || this.generateId();
|
|
110
|
-
const newDoc = {
|
|
111
|
-
...(data as Record<string, unknown>),
|
|
112
|
-
id,
|
|
113
|
-
createdAt: now,
|
|
114
|
-
updatedAt: now,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
this.db
|
|
118
|
-
.prepare(
|
|
119
|
-
"INSERT INTO documents (id, collection, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
120
|
-
)
|
|
121
|
-
.run(id, slug, JSON.stringify(newDoc), now, now);
|
|
122
|
-
|
|
123
|
-
return newDoc as T;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async update<T = any>(
|
|
127
|
-
slug: string,
|
|
128
|
-
id: string,
|
|
129
|
-
data: Partial<T>,
|
|
130
|
-
): Promise<T | null> {
|
|
131
|
-
const existing = await this.findById<T>(slug, id);
|
|
132
|
-
if (!existing) return null;
|
|
133
|
-
|
|
134
|
-
const now = this.getTimestamp();
|
|
135
|
-
const updated = { ...existing, ...data, id, updatedAt: now };
|
|
136
|
-
|
|
137
|
-
this.db
|
|
138
|
-
.prepare(
|
|
139
|
-
"UPDATE documents SET data = ?, updated_at = ? WHERE collection = ? AND id = ?",
|
|
140
|
-
)
|
|
141
|
-
.run(JSON.stringify(updated), now, slug, id);
|
|
142
|
-
|
|
143
|
-
return updated as T;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async delete(slug: string, id: string): Promise<boolean> {
|
|
147
|
-
this.db
|
|
148
|
-
.prepare("DELETE FROM documents WHERE collection = ? AND id = ?")
|
|
149
|
-
.run(slug, id);
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async findGlobal<T = any>(slug: string): Promise<T> {
|
|
154
|
-
const global = this.db
|
|
155
|
-
.prepare("SELECT data FROM globals WHERE slug = ?")
|
|
156
|
-
.get(slug) as { data: string } | undefined;
|
|
157
|
-
return global ? (JSON.parse(global.data) as T) : ({} as T);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async updateGlobal<T = any>(slug: string, data: Partial<T>): Promise<T> {
|
|
161
|
-
const now = this.getTimestamp();
|
|
162
|
-
const current = await this.findGlobal<T>(slug);
|
|
163
|
-
const updated = { ...current, ...data };
|
|
164
|
-
|
|
165
|
-
this.db
|
|
166
|
-
.prepare(
|
|
167
|
-
"INSERT OR REPLACE INTO globals (slug, data, updated_at) VALUES (?, ?, ?)",
|
|
168
|
-
)
|
|
169
|
-
.run(slug, JSON.stringify(updated), now);
|
|
170
|
-
|
|
171
|
-
return updated as T;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async seedGlobal(slug: string, data: any): Promise<void> {
|
|
175
|
-
const now = this.getTimestamp();
|
|
176
|
-
this.db
|
|
177
|
-
.prepare(
|
|
178
|
-
"INSERT OR IGNORE INTO globals (slug, data, updated_at) VALUES (?, ?, ?)",
|
|
179
|
-
)
|
|
180
|
-
.run(slug, JSON.stringify(data), now);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async count(slug: string): Promise<number> {
|
|
184
|
-
const result = this.db
|
|
185
|
-
.prepare("SELECT COUNT(*) as cnt FROM documents WHERE collection = ?")
|
|
186
|
-
.get(slug) as { cnt: number };
|
|
187
|
-
return result?.cnt || 0;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async seed(slug: string, docs: any[]): Promise<void> {
|
|
191
|
-
const now = this.getTimestamp();
|
|
192
|
-
const stmt = this.db.prepare(
|
|
193
|
-
"INSERT INTO documents (id, collection, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
for (let i = 0; i < docs.length; i++) {
|
|
197
|
-
const doc = docs[i];
|
|
198
|
-
const id = doc.id || `${slug}-${i + 1}`;
|
|
199
|
-
stmt.run(
|
|
200
|
-
id,
|
|
201
|
-
slug,
|
|
202
|
-
JSON.stringify({
|
|
203
|
-
...doc,
|
|
204
|
-
createdAt: doc.createdAt || now,
|
|
205
|
-
updatedAt: doc.updatedAt || now,
|
|
206
|
-
}),
|
|
207
|
-
now,
|
|
208
|
-
now,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async isSeeded(slug: string): Promise<boolean> {
|
|
214
|
-
return (await this.count(slug)) > 0;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
let sqliteContentAdapter: DrizzleSQLiteAdapter | null = null;
|
|
219
|
-
|
|
220
|
-
export function getSQLiteContentAdapter(
|
|
221
|
-
config?: DatabaseConfig,
|
|
222
|
-
): DrizzleSQLiteAdapter {
|
|
223
|
-
if (!sqliteContentAdapter) {
|
|
224
|
-
sqliteContentAdapter = new DrizzleSQLiteAdapter(config);
|
|
225
|
-
}
|
|
226
|
-
return sqliteContentAdapter;
|
|
227
|
-
}
|