create-aphex 0.1.1 → 0.2.0

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 (104) hide show
  1. package/dist/index.js +9 -88
  2. package/package.json +4 -4
  3. package/templates/base/.dockerignore +0 -44
  4. package/templates/base/.env.example +0 -38
  5. package/templates/base/CHANGELOG.md +0 -119
  6. package/templates/base/Dockerfile +0 -69
  7. package/templates/base/README.md +0 -147
  8. package/templates/base/aphex.config.ts +0 -45
  9. package/templates/base/docker-compose.yml +0 -31
  10. package/templates/base/drizzle/0000_tiny_redwing.sql +0 -226
  11. package/templates/base/drizzle/meta/0000_snapshot.json +0 -1379
  12. package/templates/base/drizzle/meta/_journal.json +0 -13
  13. package/templates/base/drizzle.config.ts +0 -17
  14. package/templates/base/package.json +0 -81
  15. package/templates/base/prod.docker-compose.yml +0 -61
  16. package/templates/base/src/app.css +0 -32
  17. package/templates/base/src/app.d.ts +0 -26
  18. package/templates/base/src/app.html +0 -11
  19. package/templates/base/src/hooks.server.ts +0 -52
  20. package/templates/base/src/lib/assets/favicon.svg +0 -5
  21. package/templates/base/src/lib/auth-client.ts +0 -19
  22. package/templates/base/src/lib/index.ts +0 -1
  23. package/templates/base/src/lib/schemaTypes/index.ts +0 -11
  24. package/templates/base/src/lib/schemaTypes/post.ts +0 -47
  25. package/templates/base/src/lib/server/auth/better-auth/instance.ts +0 -165
  26. package/templates/base/src/lib/server/auth/index.ts +0 -25
  27. package/templates/base/src/lib/server/auth/instance.ts +0 -7
  28. package/templates/base/src/lib/server/auth/service.ts +0 -486
  29. package/templates/base/src/lib/server/cache/index.ts +0 -10
  30. package/templates/base/src/lib/server/db/auth-schema.ts +0 -86
  31. package/templates/base/src/lib/server/db/cms-schema.ts +0 -23
  32. package/templates/base/src/lib/server/db/index.ts +0 -55
  33. package/templates/base/src/lib/server/db/schema.ts +0 -3
  34. package/templates/base/src/lib/server/email/components/EmailLayout.svelte +0 -24
  35. package/templates/base/src/lib/server/email/components/EmailVerification.svelte +0 -26
  36. package/templates/base/src/lib/server/email/components/Invitation.svelte +0 -30
  37. package/templates/base/src/lib/server/email/components/PasswordReset.svelte +0 -28
  38. package/templates/base/src/lib/server/email/index.ts +0 -28
  39. package/templates/base/src/lib/server/email/invitation-hook.ts +0 -56
  40. package/templates/base/src/lib/server/email/renderer.ts +0 -13
  41. package/templates/base/src/lib/server/email/templates/email-verification.ts +0 -8
  42. package/templates/base/src/lib/server/email/templates/invitation.ts +0 -8
  43. package/templates/base/src/lib/server/email/templates/password-reset.ts +0 -7
  44. package/templates/base/src/lib/server/services/organization.ts +0 -68
  45. package/templates/base/src/lib/server/storage/index.ts +0 -28
  46. package/templates/base/src/lib/stores/activeTab.svelte.ts +0 -14
  47. package/templates/base/src/lib/utils/imageBuilder.ts +0 -3
  48. package/templates/base/src/lib/utils/slug.ts +0 -33
  49. package/templates/base/src/routes/(protected)/admin/+layout.server.ts +0 -86
  50. package/templates/base/src/routes/(protected)/admin/+layout.svelte +0 -78
  51. package/templates/base/src/routes/(protected)/admin/+page.server.ts +0 -60
  52. package/templates/base/src/routes/(protected)/admin/+page.svelte +0 -34
  53. package/templates/base/src/routes/(protected)/admin/organizations/+page.server.ts +0 -39
  54. package/templates/base/src/routes/(protected)/admin/organizations/+page.svelte +0 -31
  55. package/templates/base/src/routes/(protected)/admin/organizations/_components/CreateOrganization.svelte +0 -141
  56. package/templates/base/src/routes/(protected)/admin/organizations/_components/OrganizationsList.svelte +0 -102
  57. package/templates/base/src/routes/(protected)/admin/settings/+layout.server.ts +0 -42
  58. package/templates/base/src/routes/(protected)/admin/settings/+layout.svelte +0 -116
  59. package/templates/base/src/routes/(protected)/admin/settings/+page.server.ts +0 -16
  60. package/templates/base/src/routes/(protected)/admin/settings/+page.svelte +0 -32
  61. package/templates/base/src/routes/(protected)/admin/settings/_components/AccountSettings.svelte +0 -177
  62. package/templates/base/src/routes/(protected)/admin/settings/_components/ApiKeysSettings.svelte +0 -472
  63. package/templates/base/src/routes/(protected)/admin/settings/_components/OrganizationsSettings.svelte +0 -182
  64. package/templates/base/src/routes/(protected)/admin/settings/account/+page.server.ts +0 -25
  65. package/templates/base/src/routes/(protected)/admin/settings/account/+page.svelte +0 -23
  66. package/templates/base/src/routes/(protected)/admin/settings/api-keys/+page.server.ts +0 -54
  67. package/templates/base/src/routes/(protected)/admin/settings/api-keys/+page.svelte +0 -14
  68. package/templates/base/src/routes/(protected)/admin/settings/members/+page.server.ts +0 -31
  69. package/templates/base/src/routes/(protected)/admin/settings/members/+page.svelte +0 -338
  70. package/templates/base/src/routes/(protected)/admin/settings/roles/+page.server.ts +0 -17
  71. package/templates/base/src/routes/(protected)/admin/settings/roles/+page.svelte +0 -339
  72. package/templates/base/src/routes/+layout.svelte +0 -12
  73. package/templates/base/src/routes/+page.svelte +0 -2
  74. package/templates/base/src/routes/api/[...slug]/+server.ts +0 -35
  75. package/templates/base/src/routes/api/instance-settings/+server.ts +0 -70
  76. package/templates/base/src/routes/api/invitations/+server.ts +0 -54
  77. package/templates/base/src/routes/api/invitations/[id]/accept/+server.ts +0 -54
  78. package/templates/base/src/routes/api/invitations/[id]/reject/+server.ts +0 -49
  79. package/templates/base/src/routes/api/settings/api-keys/+server.ts +0 -57
  80. package/templates/base/src/routes/api/settings/api-keys/[id]/+server.ts +0 -53
  81. package/templates/base/src/routes/god-mode/+layout.server.ts +0 -32
  82. package/templates/base/src/routes/god-mode/+layout.svelte +0 -155
  83. package/templates/base/src/routes/god-mode/+page.server.ts +0 -10
  84. package/templates/base/src/routes/god-mode/+page.svelte +0 -32
  85. package/templates/base/src/routes/god-mode/_components/CreateOrganization.svelte +0 -141
  86. package/templates/base/src/routes/god-mode/_components/OrganizationsList.svelte +0 -169
  87. package/templates/base/src/routes/god-mode/organizations/+page.server.ts +0 -43
  88. package/templates/base/src/routes/god-mode/organizations/+page.svelte +0 -74
  89. package/templates/base/src/routes/invitations/+page.server.ts +0 -48
  90. package/templates/base/src/routes/invitations/+page.svelte +0 -264
  91. package/templates/base/src/routes/invite/[token]/+page.server.ts +0 -59
  92. package/templates/base/src/routes/invite/[token]/+page.svelte +0 -109
  93. package/templates/base/src/routes/login/+page.server.ts +0 -14
  94. package/templates/base/src/routes/login/+page.svelte +0 -332
  95. package/templates/base/src/routes/media/[id]/[filename]/+server.ts +0 -3
  96. package/templates/base/src/routes/reset-password/[token]/+page.server.ts +0 -15
  97. package/templates/base/src/routes/reset-password/[token]/+page.svelte +0 -179
  98. package/templates/base/src/routes/verify-email/+page.svelte +0 -41
  99. package/templates/base/static/images/aphex-darkmode.png +0 -0
  100. package/templates/base/static/images/aphex-lightmode.png +0 -0
  101. package/templates/base/static/robots.txt +0 -3
  102. package/templates/base/svelte.config.js +0 -19
  103. package/templates/base/tsconfig.json +0 -20
  104. package/templates/base/vite.config.ts +0 -138
@@ -1,486 +0,0 @@
1
- import { auth } from './instance.js';
2
- import { apikey, user } from '../db/auth-schema';
3
- import { drizzleDb } from '../db';
4
- import { eq } from 'drizzle-orm';
5
- import type {
6
- SessionAuth,
7
- PartialSessionAuth,
8
- ApiKeyAuth,
9
- CMSUser,
10
- NewUserProfileData,
11
- DatabaseAdapter
12
- } from '@aphexcms/cms-core/server';
13
- import { AuthError } from '@aphexcms/cms-core/server';
14
- import { cmsLogger } from '@aphexcms/cms-core';
15
-
16
- // This is the new AuthService that centralizes all auth-related server operations.
17
- // It uses dependency injection for the DatabaseAdapter, making it more testable and decoupled.
18
-
19
- export interface ApiKey {
20
- id: string;
21
- name: string | null;
22
- organizationId?: string;
23
- [key: string]: any;
24
- }
25
-
26
- export interface ApiKeyWithSecret extends ApiKey {
27
- key: string;
28
- organizationId: string; // Make required (override optional from ApiKey)
29
- }
30
-
31
- export interface CreateApiKeyData {
32
- name: string;
33
- /**
34
- * Legacy coarse-grained scopes. Optional when `capabilities` is supplied —
35
- * at least one of the two must be present (validator enforces this).
36
- */
37
- permissions?: string[];
38
- /** Fine-grained capability allowlist for this key. */
39
- capabilities?: string[];
40
- expiresInDays?: number;
41
- }
42
-
43
- export interface AuthService {
44
- getSession(
45
- request: Request,
46
- db: DatabaseAdapter
47
- ): Promise<SessionAuth | PartialSessionAuth | null>;
48
- requireSession(request: Request, db: DatabaseAdapter): Promise<SessionAuth>;
49
- validateApiKey(request: Request): Promise<ApiKeyAuth | null>;
50
- requireApiKey(
51
- request: Request,
52
- db: DatabaseAdapter,
53
- permission?: 'read' | 'write'
54
- ): Promise<ApiKeyAuth>;
55
- listApiKeys(db: DatabaseAdapter, userId: string): Promise<ApiKey[]>;
56
- createApiKey(
57
- userId: string,
58
- organizationId: string,
59
- data: CreateApiKeyData
60
- ): Promise<ApiKeyWithSecret>;
61
- deleteApiKey(userId: string, keyId: string): Promise<boolean>;
62
- getUserById(userId: string): Promise<{ id: string; name?: string; email: string } | null>;
63
- getUserByEmail(email: string): Promise<{ id: string; name?: string; email: string } | null>;
64
- changeUserName(userId: string, name: string): Promise<void>;
65
- requestPasswordReset(email: string, redirectTo?: string): Promise<void>;
66
- resetPassword(token: string, newPassword: string): Promise<void>;
67
- }
68
-
69
- export const authService: AuthService = {
70
- async getSession(
71
- request: Request,
72
- db: DatabaseAdapter
73
- ): Promise<SessionAuth | PartialSessionAuth | null> {
74
- try {
75
- cmsLogger.debug('[AuthService]', 'getSession called');
76
- // 1. Get the base user session from the auth provider
77
- const session = await auth.api.getSession({ headers: request.headers });
78
- if (!session) {
79
- cmsLogger.debug('[AuthService]', 'No active session found from auth provider');
80
- return null;
81
- }
82
- cmsLogger.debug('[AuthService]', `Found session for user ${session.user.id}`);
83
-
84
- // 2. Get the corresponding CMS user profile from our database
85
- cmsLogger.debug('[AuthService]', `Checking for user profile for ${session.user.id}`);
86
- let userProfile = await db.findUserProfileById(session.user.id);
87
-
88
- // 3. If no profile exists, create one (lazy sync)
89
- if (!userProfile) {
90
- cmsLogger.info(
91
- '[AuthService]',
92
- `User profile not found for ${session.user.id}. Creating one now (lazy sync).`
93
- );
94
-
95
- // Check if this is the first user in the system
96
- const hasExistingUsers =
97
- typeof db.hasAnyUserProfiles === 'function' ? await db.hasAnyUserProfiles() : false;
98
- const isFirstUser = !hasExistingUsers;
99
-
100
- const newUserProfile: NewUserProfileData = {
101
- userId: session.user.id,
102
- role: isFirstUser ? 'super_admin' : 'editor' // First user gets super_admin, others get editor
103
- };
104
- userProfile = await db.createUserProfile(newUserProfile);
105
- cmsLogger.info(
106
- '[AuthService]',
107
- `Successfully created user profile for ${session.user.id}${isFirstUser ? ' with SUPER_ADMIN role' : ''}`
108
- );
109
- }
110
-
111
- // 4. Combine the two into the final CMSUser object
112
- const cmsUser: CMSUser = {
113
- id: session.user.id,
114
- email: session.user.email,
115
- name: session.user.name ?? undefined,
116
- image: session.user.image ?? undefined,
117
- role: userProfile.role,
118
- preferences: userProfile.preferences
119
- };
120
- cmsLogger.debug('[AuthService]', `Successfully assembled CMSUser for ${session.user.id}`);
121
-
122
- // 5. Get the user's active organization from their session
123
- cmsLogger.debug('[AuthService]', `Fetching active organization for ${session.user.id}`);
124
- const userSession = await db.findUserSession(session.user.id);
125
-
126
- // If no session exists, get the user's first organization as the default
127
- if (!userSession) {
128
- cmsLogger.debug('[AuthService]', `No user session found. Fetching user's organizations.`);
129
- const userOrgs = await db.findUserOrganizations(session.user.id);
130
-
131
- if (userOrgs.length === 0) {
132
- // If this is a super_admin with no orgs, create a default organization
133
- if (cmsUser.role === 'super_admin') {
134
- cmsLogger.info(
135
- '[AuthService]',
136
- `Super admin ${session.user.id} has no organizations. Creating default organization.`
137
- );
138
-
139
- const defaultOrg = await db.createOrganization({
140
- name: 'Default Organization',
141
- slug: 'default',
142
- createdBy: session.user.id
143
- });
144
-
145
- // Seed built-in roles before assigning membership — member
146
- // rows reference roles by name, so they must exist first.
147
- await db.seedBuiltinRoles(defaultOrg.id);
148
-
149
- // Add super admin as owner
150
- await db.addMember({
151
- organizationId: defaultOrg.id,
152
- userId: session.user.id,
153
- role: 'owner'
154
- });
155
-
156
- // Set as active organization
157
- await db.updateUserSession(session.user.id, defaultOrg.id);
158
-
159
- cmsLogger.info(
160
- '[AuthService]',
161
- `Created default organization ${defaultOrg.id} for super admin.`
162
- );
163
- return {
164
- type: 'session',
165
- user: cmsUser,
166
- session: {
167
- id: session.session.id,
168
- expiresAt: session.session.expiresAt
169
- },
170
- organizationId: defaultOrg.id,
171
- organizationRole: 'owner'
172
- };
173
- }
174
-
175
- // User has no organizations — return partial session without org context
176
- // Routes like /invitations can use this to identify the user
177
- cmsLogger.debug(
178
- '[AuthService]',
179
- `User ${session.user.id} has no organizations. Returning partial session.`
180
- );
181
- return {
182
- type: 'partial_session',
183
- user: cmsUser,
184
- session: {
185
- id: session.session.id,
186
- expiresAt: session.session.expiresAt
187
- }
188
- };
189
- }
190
-
191
- // Set the first organization as active
192
- const firstOrg = userOrgs[0]!;
193
- await db.updateUserSession(session.user.id, firstOrg.organization.id);
194
-
195
- cmsLogger.debug(
196
- '[AuthService]',
197
- `Set first organization ${firstOrg.organization.id} as active.`
198
- );
199
- return {
200
- type: 'session',
201
- user: cmsUser,
202
- session: {
203
- id: session.session.id,
204
- expiresAt: session.session.expiresAt
205
- },
206
- organizationId: firstOrg.organization.id,
207
- organizationRole: firstOrg.member.role
208
- };
209
- }
210
-
211
- // 6. Get the user's membership in the active organization
212
- cmsLogger.debug(
213
- '[AuthService]',
214
- `Getting membership for org ${userSession.activeOrganizationId}`
215
- );
216
- const membership = await db.findUserMembership(
217
- session.user.id,
218
- userSession.activeOrganizationId!
219
- );
220
-
221
- if (!membership) {
222
- cmsLogger.error(
223
- '[AuthService]',
224
- `User ${session.user.id} is not a member of org ${userSession.activeOrganizationId}`
225
- );
226
- throw new AuthError('kicked_from_org', 'User is not a member of the active organization');
227
- }
228
-
229
- // 7. Return the complete SessionAuth object with organization context
230
- return {
231
- type: 'session',
232
- user: cmsUser,
233
- session: {
234
- id: session.session.id,
235
- expiresAt: session.session.expiresAt
236
- },
237
- organizationId: userSession.activeOrganizationId!,
238
- organizationRole: membership.role
239
- };
240
- } catch (error) {
241
- // Re-throw AuthError to preserve error codes
242
- if (error instanceof AuthError) {
243
- throw error;
244
- }
245
- cmsLogger.error('[AuthService]', 'Error in getSession:', error);
246
- return null;
247
- }
248
- },
249
-
250
- async requireSession(request: Request, db: DatabaseAdapter): Promise<SessionAuth> {
251
- const session = await this.getSession(request, db);
252
- if (!session) {
253
- throw new AuthError('no_session', 'Unauthorized: Session required');
254
- }
255
- // User is authenticated but has no organization — redirect to invitations
256
- if (session.type === 'partial_session') {
257
- throw new AuthError('pending_invitations', 'User has pending invitations to review');
258
- }
259
- return session;
260
- },
261
-
262
- async validateApiKey(request: Request): Promise<ApiKeyAuth | null> {
263
- try {
264
- const apiKeyHeader = request.headers.get('x-api-key');
265
- if (!apiKeyHeader) return null;
266
-
267
- const result = await auth.api.verifyApiKey({ body: { key: apiKeyHeader } });
268
- if (!result.valid || !result.key) return null;
269
-
270
- // Better Auth stores metadata in the key object
271
- const metadata = result.key.metadata || {};
272
- const permissions = metadata.permissions || ['read', 'write'];
273
- // Fine-grained capability allowlist — optional. When present, takes
274
- // precedence over `permissions` at the capability-resolution layer
275
- // (see resolveCapabilities in cms-core).
276
- const capabilities = Array.isArray(metadata.capabilities) ? metadata.capabilities : undefined;
277
- const organizationId = metadata.organizationId;
278
-
279
- if (!organizationId) {
280
- cmsLogger.error(
281
- '[AuthService]',
282
- `API key ${result.key.id} missing organizationId in metadata`
283
- );
284
- return null;
285
- }
286
-
287
- return {
288
- type: 'api_key',
289
- keyId: result.key.id,
290
- name: result.key.name || 'Unnamed Key',
291
- permissions,
292
- capabilities,
293
- organizationId,
294
- lastUsedAt: result.key.lastRequest || undefined
295
- };
296
- } catch (error) {
297
- cmsLogger.error('[AuthService]', 'validateApiKey error:', error);
298
- return null;
299
- }
300
- },
301
-
302
- async requireApiKey(
303
- request: Request,
304
- db: DatabaseAdapter,
305
- permission?: 'read' | 'write'
306
- ): Promise<ApiKeyAuth> {
307
- const apiKeyAuth = await this.validateApiKey(request);
308
- if (!apiKeyAuth) {
309
- throw new Error('Unauthorized: Valid API key required');
310
- }
311
-
312
- if (permission && !apiKeyAuth.permissions.includes(permission)) {
313
- throw new Error(`Forbidden: API key missing ${permission} permission`);
314
- }
315
-
316
- return apiKeyAuth;
317
- },
318
-
319
- async listApiKeys(db: DatabaseAdapter, userId: string): Promise<ApiKey[]> {
320
- // Query the apikey table directly using drizzleDb (not the adapter)
321
- const userApiKeys = await drizzleDb.query.apikey.findMany({
322
- where: eq(apikey.referenceId, userId),
323
- columns: {
324
- id: true,
325
- name: true,
326
- metadata: true,
327
- expiresAt: true,
328
- lastRequest: true,
329
- createdAt: true
330
- },
331
- orderBy: (apikey, { desc }) => [desc(apikey.createdAt)]
332
- });
333
-
334
- return userApiKeys.map((key) => {
335
- const metadata =
336
- typeof key.metadata === 'string' ? JSON.parse(key.metadata) : (key.metadata as any) || {};
337
- return {
338
- ...key,
339
- permissions: metadata.permissions || [],
340
- capabilities: Array.isArray(metadata.capabilities) ? metadata.capabilities : undefined,
341
- organizationId: metadata.organizationId
342
- };
343
- });
344
- },
345
-
346
- async createApiKey(
347
- userId: string,
348
- organizationId: string,
349
- data: CreateApiKeyData
350
- ): Promise<ApiKeyWithSecret> {
351
- const expiresIn = data.expiresInDays ? data.expiresInDays * 24 * 60 * 60 : undefined;
352
-
353
- const result = await auth.api.createApiKey({
354
- body: {
355
- userId: userId,
356
- name: data.name,
357
- expiresIn,
358
- metadata: {
359
- // Keep both fields for forward/backward compatibility:
360
- // older validators read `permissions`, newer ones prefer
361
- // `capabilities` when the caller supplied them.
362
- permissions: data.permissions ?? ['read'],
363
- capabilities: data.capabilities,
364
- organizationId: organizationId
365
- }
366
- }
367
- });
368
-
369
- if (!result || !result.id) {
370
- throw new Error('Failed to create API key');
371
- }
372
-
373
- return {
374
- id: result.id,
375
- name: result.name,
376
- key: result.key,
377
- permissions: data.permissions ?? ['read'],
378
- capabilities: data.capabilities,
379
- organizationId: organizationId,
380
- expiresAt: result.expiresAt,
381
- createdAt: result.createdAt
382
- };
383
- },
384
-
385
- async deleteApiKey(userId: string, keyId: string): Promise<boolean> {
386
- // This will be implemented when we refactor the [id] route
387
- cmsLogger.debug('[AuthService]', `Deleting key ${keyId} for user ${userId}`);
388
- return Promise.resolve(true);
389
- },
390
-
391
- async getUserById(userId: string): Promise<{ id: string; name?: string; email: string } | null> {
392
- try {
393
- const userRecord = await drizzleDb.query.user.findFirst({
394
- where: eq(user.id, userId),
395
- columns: {
396
- id: true,
397
- name: true,
398
- email: true
399
- }
400
- });
401
-
402
- if (!userRecord) {
403
- return null;
404
- }
405
-
406
- return {
407
- id: userRecord.id,
408
- name: userRecord.name ?? undefined,
409
- email: userRecord.email
410
- };
411
- } catch (error) {
412
- cmsLogger.error('[AuthService]', 'Error fetching user by ID:', error);
413
- return null;
414
- }
415
- },
416
-
417
- async getUserByEmail(
418
- email: string
419
- ): Promise<{ id: string; name?: string; email: string } | null> {
420
- try {
421
- const userRecord = await drizzleDb.query.user.findFirst({
422
- where: eq(user.email, email.toLowerCase()),
423
- columns: {
424
- id: true,
425
- name: true,
426
- email: true
427
- }
428
- });
429
-
430
- if (!userRecord) {
431
- return null;
432
- }
433
-
434
- return {
435
- id: userRecord.id,
436
- name: userRecord.name ?? undefined,
437
- email: userRecord.email
438
- };
439
- } catch (error) {
440
- cmsLogger.error('[AuthService]', 'Error fetching user by email:', error);
441
- return null;
442
- }
443
- },
444
-
445
- async changeUserName(userId: string, name: string): Promise<void> {
446
- await drizzleDb
447
- .update(user)
448
- .set({
449
- name,
450
- updatedAt: new Date()
451
- })
452
- .where(eq(user.id, userId));
453
- },
454
-
455
- async requestPasswordReset(email: string, redirectTo?: string): Promise<void> {
456
- try {
457
- await auth.api.requestPasswordReset({
458
- body: {
459
- email,
460
- redirectTo
461
- }
462
- });
463
-
464
- // TODO: Send password reset email via email adapter - for true agnoticism
465
- // The email adapter can be accessed from event.locals.aphexCMS.emailAdapter
466
- // For now, Better Auth handles the email sending internally
467
- } catch (error) {
468
- cmsLogger.error('[AuthService]', 'Error requesting password reset:', error);
469
- throw error;
470
- }
471
- },
472
-
473
- async resetPassword(token: string, newPassword: string): Promise<void> {
474
- try {
475
- await auth.api.resetPassword({
476
- body: {
477
- newPassword,
478
- token
479
- }
480
- });
481
- } catch (error) {
482
- cmsLogger.error('[AuthService]', 'Error resetting password:', error);
483
- throw error;
484
- }
485
- }
486
- };
@@ -1,10 +0,0 @@
1
- import { InMemoryCacheAdapter } from '@aphexcms/cms-core/server';
2
-
3
- /**
4
- * Shared cache adapter singleton.
5
- * Used by both CMS config (document caching) and auth (API key caching).
6
- * Set to null to disable caching.
7
- */
8
- export const cacheAdapter: InMemoryCacheAdapter | null = new InMemoryCacheAdapter({
9
- maxSize: 5000
10
- });
@@ -1,86 +0,0 @@
1
- import { pgTable, text, timestamp, boolean, integer } from 'drizzle-orm/pg-core';
2
-
3
- export const user = pgTable('user', {
4
- id: text('id').primaryKey(),
5
- name: text('name').notNull(),
6
- email: text('email').notNull().unique(),
7
- emailVerified: boolean('email_verified').default(false).notNull(),
8
- image: text('image'),
9
- createdAt: timestamp('created_at').defaultNow().notNull(),
10
- updatedAt: timestamp('updated_at')
11
- .defaultNow()
12
- .$onUpdate(() => /* @__PURE__ */ new Date())
13
- .notNull()
14
- });
15
-
16
- export const session = pgTable('session', {
17
- id: text('id').primaryKey(),
18
- expiresAt: timestamp('expires_at').notNull(),
19
- token: text('token').notNull().unique(),
20
- createdAt: timestamp('created_at').defaultNow().notNull(),
21
- updatedAt: timestamp('updated_at')
22
- .$onUpdate(() => /* @__PURE__ */ new Date())
23
- .notNull(),
24
- ipAddress: text('ip_address'),
25
- userAgent: text('user_agent'),
26
- userId: text('user_id')
27
- .notNull()
28
- .references(() => user.id, { onDelete: 'cascade' })
29
- });
30
-
31
- export const account = pgTable('account', {
32
- id: text('id').primaryKey(),
33
- accountId: text('account_id').notNull(),
34
- providerId: text('provider_id').notNull(),
35
- userId: text('user_id')
36
- .notNull()
37
- .references(() => user.id, { onDelete: 'cascade' }),
38
- accessToken: text('access_token'),
39
- refreshToken: text('refresh_token'),
40
- idToken: text('id_token'),
41
- accessTokenExpiresAt: timestamp('access_token_expires_at'),
42
- refreshTokenExpiresAt: timestamp('refresh_token_expires_at'),
43
- scope: text('scope'),
44
- password: text('password'),
45
- createdAt: timestamp('created_at').defaultNow().notNull(),
46
- updatedAt: timestamp('updated_at')
47
- .$onUpdate(() => /* @__PURE__ */ new Date())
48
- .notNull()
49
- });
50
-
51
- export const verification = pgTable('verification', {
52
- id: text('id').primaryKey(),
53
- identifier: text('identifier').notNull(),
54
- value: text('value').notNull(),
55
- expiresAt: timestamp('expires_at').notNull(),
56
- createdAt: timestamp('created_at').defaultNow().notNull(),
57
- updatedAt: timestamp('updated_at')
58
- .defaultNow()
59
- .$onUpdate(() => /* @__PURE__ */ new Date())
60
- .notNull()
61
- });
62
-
63
- export const apikey = pgTable('apikey', {
64
- id: text('id').primaryKey(),
65
- configId: text('config_id').notNull().default('default'),
66
- name: text('name'),
67
- start: text('start'),
68
- prefix: text('prefix'),
69
- key: text('key').notNull(),
70
- referenceId: text('reference_id').notNull(),
71
- refillInterval: integer('refill_interval'),
72
- refillAmount: integer('refill_amount'),
73
- lastRefillAt: timestamp('last_refill_at'),
74
- enabled: boolean('enabled').default(true),
75
- rateLimitEnabled: boolean('rate_limit_enabled').default(true),
76
- rateLimitTimeWindow: integer('rate_limit_time_window').default(86400000),
77
- rateLimitMax: integer('rate_limit_max').default(10000),
78
- requestCount: integer('request_count').default(0),
79
- remaining: integer('remaining'),
80
- lastRequest: timestamp('last_request'),
81
- expiresAt: timestamp('expires_at'),
82
- createdAt: timestamp('created_at').notNull(),
83
- updatedAt: timestamp('updated_at').notNull(),
84
- permissions: text('permissions'),
85
- metadata: text('metadata')
86
- });
@@ -1,23 +0,0 @@
1
- // Database schema for Aphex CMS using Drizzle ORM
2
- // This file combines CMS package schema with app-specific tables
3
- // Re-export CMS core schema tables from PostgreSQL adapter package
4
- // Import from /schema to avoid loading the entire adapter
5
- export {
6
- // Multi-tenancy tables
7
- organizations,
8
- organizationMembers,
9
- invitations,
10
- roles,
11
- instanceSettings,
12
- userSessions,
13
- // Content tables
14
- documents,
15
- documentVersions,
16
- assets,
17
- schemaTypes,
18
- userProfiles,
19
- // Enums
20
- documentStatusEnum,
21
- versionEventEnum,
22
- schemaTypeEnum
23
- } from '@aphexcms/postgresql-adapter/schema';
@@ -1,55 +0,0 @@
1
- import { drizzle } from 'drizzle-orm/postgres-js';
2
- import type { Logger } from 'drizzle-orm';
3
- import postgres from 'postgres';
4
- import { env } from '$env/dynamic/private';
5
- import { createPostgreSQLProvider, pgConnectionUrl } from '@aphexcms/postgresql-adapter';
6
- import * as cmsSchema from './cms-schema';
7
- import * as authSchema from './auth-schema';
8
-
9
- const schema = {
10
- ...cmsSchema,
11
- ...authSchema
12
- };
13
-
14
- import type { DatabaseAdapter } from '@aphexcms/cms-core/server';
15
-
16
- const SLOW_QUERY_THRESHOLD_MS = parseInt(env.SLOW_QUERY_MS || '100');
17
-
18
- class SlowQueryLogger implements Logger {
19
- logQuery(query: string, params: unknown[]): void {
20
- const start = performance.now();
21
- // Store start time — Drizzle calls logQuery before execution
22
- // We use queueMicrotask to measure after the query completes
23
- queueMicrotask(() => {
24
- const duration = performance.now() - start;
25
- if (duration >= SLOW_QUERY_THRESHOLD_MS) {
26
- const truncatedQuery = query.length > 200 ? query.slice(0, 200) + '...' : query;
27
- console.warn(`[SLOW QUERY] ${duration.toFixed(1)}ms — ${truncatedQuery}`);
28
- }
29
- });
30
- }
31
- }
32
-
33
- const databaseUrl = pgConnectionUrl(env);
34
-
35
- export const client = postgres(databaseUrl, {
36
- max: 50,
37
- idle_timeout: 20, // Release idle connections after 20s
38
- connect_timeout: 10, // Fail fast if can't connect in 10s
39
- max_lifetime: 60 * 5 // Recycle connections every 5 minutes
40
- });
41
- export const drizzleDb = drizzle(client, {
42
- schema,
43
- logger: env.ENABLE_QUERY_LOG === 'true' ? new SlowQueryLogger() : undefined
44
- });
45
-
46
- const provider = createPostgreSQLProvider({
47
- client,
48
- multiTenancy: {
49
- enableRLS: true,
50
- enableHierarchy: true
51
- }
52
- });
53
-
54
- const adapter = provider.createAdapter();
55
- export const db = adapter as DatabaseAdapter;
@@ -1,3 +0,0 @@
1
- // 1. Import the separate schema parts
2
- export * from './cms-schema';
3
- export * from './auth-schema';
@@ -1,24 +0,0 @@
1
- <script lang="ts">
2
- import { Html, Head, Body, Preview, Container, Section, Text, Hr } from 'better-svelte-email';
3
- import type { Snippet } from 'svelte';
4
-
5
- let { preview = '', children }: { preview?: string; children: Snippet } = $props();
6
- </script>
7
-
8
- <Html>
9
- <Head />
10
- <Body class="bg-[#f6f9fc] font-sans">
11
- {#if preview}
12
- <Preview {preview} />
13
- {/if}
14
- <Container class="mx-auto my-[40px] max-w-[580px] rounded-[8px] bg-white p-[32px]">
15
- {@render children()}
16
-
17
- <Hr class="my-[24px] border-[#e6ebf1]" />
18
-
19
- <Section>
20
- <Text class="text-[12px] leading-[16px] text-[#8898aa]">Sent by Aphex CMS</Text>
21
- </Section>
22
- </Container>
23
- </Body>
24
- </Html>