sitepaige-mcp-server 1.1.0 → 1.2.1

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 (51) hide show
  1. package/components/headerlogin.tsx +10 -28
  2. package/components/profile.tsx +302 -46
  3. package/components/slideshow.tsx +1 -9
  4. package/defaultapp/api/Auth/resend-verification/route.ts +3 -3
  5. package/defaultapp/api/Auth/route.ts +2 -2
  6. package/defaultapp/api/Auth/signup/route.ts +3 -3
  7. package/defaultapp/api/Auth/verify-email/route.ts +5 -5
  8. package/defaultapp/api/admin/users/route.ts +3 -3
  9. package/defaultapp/{db-users.ts → api/db-users.ts} +1 -1
  10. package/defaultapp/api/profile/route.ts +154 -0
  11. package/defaultapp/profile/page.tsx +6 -0
  12. package/dist/blueprintWriter.js.map +1 -1
  13. package/dist/components/headerlogin.tsx +10 -28
  14. package/dist/components/profile.tsx +302 -46
  15. package/dist/components/slideshow.tsx +1 -9
  16. package/dist/defaultapp/api/Auth/resend-verification/route.ts +3 -3
  17. package/dist/defaultapp/api/Auth/route.ts +2 -2
  18. package/dist/defaultapp/api/Auth/signup/route.ts +3 -3
  19. package/dist/defaultapp/api/Auth/verify-email/route.ts +5 -5
  20. package/dist/defaultapp/api/admin/users/route.ts +3 -3
  21. package/dist/defaultapp/api/csrf.ts +111 -0
  22. package/dist/defaultapp/api/db-mysql.ts +183 -0
  23. package/dist/defaultapp/api/db-password-auth.ts +362 -0
  24. package/dist/defaultapp/api/db-postgres.ts +189 -0
  25. package/dist/defaultapp/api/db-sqlite.ts +335 -0
  26. package/dist/defaultapp/api/db-users.ts +520 -0
  27. package/dist/defaultapp/api/db.ts +149 -0
  28. package/dist/defaultapp/api/profile/route.ts +154 -0
  29. package/dist/defaultapp/api/storage/email.ts +162 -0
  30. package/dist/defaultapp/api/storage/files.ts +160 -0
  31. package/dist/defaultapp/db-users.ts +1 -1
  32. package/dist/defaultapp/profile/page.tsx +6 -0
  33. package/dist/generators/env-example-template.txt +4 -3
  34. package/dist/generators/skeleton.js +3 -5
  35. package/dist/generators/skeleton.js.map +1 -1
  36. package/dist/generators/sql.js +60 -0
  37. package/dist/generators/sql.js.map +1 -1
  38. package/dist/sitepaige.js +2 -1
  39. package/dist/sitepaige.js.map +1 -1
  40. package/package.json +1 -1
  41. package/defaultapp/admin/page.tsx +0 -6
  42. package/defaultapp/api/example-secure/route.ts +0 -100
  43. package/defaultapp/migrate.ts +0 -142
  44. /package/defaultapp/{csrf.ts → api/csrf.ts} +0 -0
  45. /package/defaultapp/{db-mysql.ts → api/db-mysql.ts} +0 -0
  46. /package/defaultapp/{db-password-auth.ts → api/db-password-auth.ts} +0 -0
  47. /package/defaultapp/{db-postgres.ts → api/db-postgres.ts} +0 -0
  48. /package/defaultapp/{db-sqlite.ts → api/db-sqlite.ts} +0 -0
  49. /package/defaultapp/{db.ts → api/db.ts} +0 -0
  50. /package/defaultapp/{storage → api/storage}/email.ts +0 -0
  51. /package/defaultapp/{storage → api/storage}/files.ts +0 -0
@@ -0,0 +1,520 @@
1
+ /*
2
+ * User Management Database Utilities
3
+ * Handles all database operations related to user management
4
+ */
5
+
6
+ import { db_init, db_query, db_migrate } from './db';
7
+ import type { DatabaseClient } from './db';
8
+ import * as crypto from 'node:crypto';
9
+
10
+ export interface User {
11
+ userid: string;
12
+ oauthid: string;
13
+ source: 'google' | 'facebook' | 'apple' | 'github' | 'userpass';
14
+ username: string;
15
+ email?: string;
16
+ avatarurl?: string;
17
+ userlevel: number; // 0: everyone, 1: registered user, 2: admin
18
+ lastlogindate: string;
19
+ createddate: string;
20
+ isactive: boolean;
21
+ }
22
+
23
+ export interface UserSession {
24
+ id: string;
25
+ sessiontoken: string;
26
+ userid: string;
27
+ expirationdate: string;
28
+ }
29
+
30
+ export interface OAuthToken {
31
+ id: string;
32
+ userid: string;
33
+ provider: 'google' | 'facebook' | 'apple' | 'github';
34
+ accesstoken: string;
35
+ refreshtoken?: string;
36
+ expiresat?: string;
37
+ createdat: string;
38
+ updatedat: string;
39
+ }
40
+
41
+ /**
42
+ * Get all users from the database
43
+ */
44
+ export async function getAllUsers(): Promise<User[]> {
45
+ const client = await db_init();
46
+
47
+ const users = await db_query(client,
48
+ `SELECT * FROM users
49
+ WHERE isactive = ?
50
+ ORDER BY userlevel DESC, username ASC`,
51
+ [1] // Use 1 instead of true for PostgreSQL compatibility
52
+ );
53
+
54
+ return users as User[];
55
+ }
56
+
57
+ /**
58
+ * Get a user by their OAuth ID
59
+ */
60
+ export async function getUserByOAuthID(oauthId: string): Promise<User | null> {
61
+ const client = await db_init();
62
+
63
+ const users = await db_query(client,
64
+ "SELECT * FROM users WHERE oauthid = ? AND isactive = ?",
65
+ [oauthId, 1] // Use 1 instead of true for PostgreSQL compatibility
66
+ );
67
+
68
+ return users.length > 0 ? users[0] as User : null;
69
+ }
70
+
71
+ /**
72
+ * Get a user by their userid
73
+ */
74
+ export async function getUserByID(userId: string): Promise<User | null> {
75
+ const client = await db_init();
76
+
77
+ const users = await db_query(client,
78
+ "SELECT * FROM users WHERE userid = ? AND isactive = ?",
79
+ [userId, 1] // Use 1 instead of true for PostgreSQL compatibility
80
+ );
81
+
82
+ return users.length > 0 ? users[0] as User : null;
83
+ }
84
+
85
+ /**
86
+ * Create or update a user
87
+ */
88
+ export async function upsertUser(
89
+ oauthId: string,
90
+ source: 'google' | 'facebook' | 'apple' | 'github' | 'userpass',
91
+ userName: string,
92
+ email?: string,
93
+ avatarUrl?: string
94
+ ): Promise<User> {
95
+ const client = await db_init();
96
+
97
+ // Check if user exists
98
+ const existingUser = await getUserByOAuthID(oauthId);
99
+
100
+ if (existingUser) {
101
+ // Update existing user
102
+ await db_query(client,
103
+ `UPDATE users
104
+ SET username = ?, email = COALESCE(?, email), avatarurl = ?,
105
+ lastlogindate = CURRENT_TIMESTAMP, source = ?
106
+ WHERE oauthid = ?`,
107
+ [userName, email, avatarUrl || '', source, oauthId]
108
+ );
109
+
110
+ return (await getUserByOAuthID(oauthId))!;
111
+ } else {
112
+ // Check if this is the first user (should be admin)
113
+ const allUsers = await db_query(client, "SELECT COUNT(*) as count FROM users");
114
+ const isFirstUser = Number(allUsers[0].count) === 0;
115
+
116
+ // Create new user
117
+ const userId = crypto.randomUUID();
118
+ const permissionLevel = isFirstUser ? 2 : 1; // First user is admin
119
+
120
+ await db_query(client,
121
+ `INSERT INTO users
122
+ (userid, oauthid, source, username, email, avatarurl, userlevel, usertier,
123
+ lastlogindate, createddate, isactive)
124
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)`,
125
+ [userId, oauthId, source, userName, email || null, avatarUrl || '', permissionLevel, 1] // Use 1 instead of true for PostgreSQL compatibility
126
+ );
127
+
128
+ return (await getUserByOAuthID(oauthId))!;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Update a user's permission level
134
+ */
135
+ export async function updateUserPermission(
136
+ userId: string,
137
+ permissionLevel: number
138
+ ): Promise<boolean> {
139
+ const client = await db_init();
140
+
141
+ // Ensure there's always at least one admin
142
+ if (permissionLevel < 2) {
143
+ const admins = await db_query(client,
144
+ "SELECT COUNT(*) as count FROM users WHERE userlevel = ? AND userid != ? AND isactive = ?",
145
+ [2, userId, 1] // Use 1 instead of true for PostgreSQL compatibility
146
+ );
147
+
148
+ if (admins[0].count === 0) {
149
+ throw new Error('Cannot demote the last admin user');
150
+ }
151
+ }
152
+
153
+ const result = await db_query(client,
154
+ "UPDATE users SET userlevel = ? WHERE userid = ?",
155
+ [permissionLevel, userId]
156
+ );
157
+
158
+ return result[0].changes > 0;
159
+ }
160
+
161
+ /**
162
+ * Delete (soft delete) a user
163
+ */
164
+ export async function deleteUser(userId: string): Promise<boolean> {
165
+ const client = await db_init();
166
+
167
+ // Ensure there's always at least one admin
168
+ const user = await getUserByID(userId);
169
+ if (user && user.userlevel === 2) {
170
+ const admins = await db_query(client,
171
+ "SELECT COUNT(*) as count FROM users WHERE userlevel = ? AND userid != ? AND isactive = ?",
172
+ [2, userId, 1] // Use 1 instead of true for PostgreSQL compatibility
173
+ );
174
+
175
+ if (admins[0].count === 0) {
176
+ throw new Error('Cannot delete the last admin user');
177
+ }
178
+ }
179
+
180
+ // Soft delete the user
181
+ const result = await db_query(client,
182
+ "UPDATE users SET isactive = ? WHERE userid = ?",
183
+ [0, userId] // Use 0 instead of false for PostgreSQL compatibility
184
+ );
185
+
186
+ return result[0].changes > 0;
187
+ }
188
+
189
+ /**
190
+ * Get user statistics
191
+ */
192
+ export async function getUserStats(): Promise<{
193
+ totalUsers: number;
194
+ admins: number;
195
+ registeredUsers: number;
196
+ guestUsers: number;
197
+ }> {
198
+ const client = await db_init();
199
+
200
+ const stats = await db_query(client, `
201
+ SELECT
202
+ COUNT(*) as totalUsers,
203
+ SUM(CASE WHEN userlevel = 2 THEN 1 ELSE 0 END) as admins,
204
+ SUM(CASE WHEN userlevel = 1 THEN 1 ELSE 0 END) as registeredUsers,
205
+ SUM(CASE WHEN userlevel = 0 THEN 1 ELSE 0 END) as guestUsers
206
+ FROM users
207
+ WHERE isactive = ?
208
+ `, [1]); // Use 1 instead of true for PostgreSQL compatibility
209
+
210
+ return stats[0];
211
+ }
212
+
213
+ /**
214
+ * Clean up expired sessions
215
+ */
216
+ export async function cleanupExpiredSessions(): Promise<number> {
217
+ const client = await db_init();
218
+
219
+ const result = await db_query(client,
220
+ "DELETE FROM usersession WHERE expirationdate::TIMESTAMP < CURRENT_TIMESTAMP"
221
+ );
222
+
223
+ return result[0].changes;
224
+ }
225
+
226
+ /**
227
+ * Store OAuth tokens securely
228
+ */
229
+ export async function storeOAuthToken(
230
+ userId: string,
231
+ provider: 'google' | 'facebook' | 'apple' | 'github',
232
+ accessToken: string,
233
+ refreshToken?: string,
234
+ expiresIn?: number
235
+ ): Promise<void> {
236
+ const client = await db_init();
237
+
238
+ const tokenId = crypto.randomUUID();
239
+ const now = new Date().toISOString();
240
+ const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000).toISOString() : null;
241
+
242
+ // Delete existing tokens for this user/provider combo
243
+ await db_query(client,
244
+ "DELETE FROM oauthtokens WHERE userid = ? AND provider = ?",
245
+ [userId, provider]
246
+ );
247
+
248
+ // Insert new token
249
+ await db_query(client,
250
+ `INSERT INTO oauthtokens
251
+ (id, userid, provider, accesstoken, refreshtoken, expiresat, createdat, updatedat)
252
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
253
+ [tokenId, userId, provider, accessToken, refreshToken || null, expiresAt, now, now]
254
+ );
255
+ }
256
+
257
+ /**
258
+ * Get OAuth token for a user
259
+ */
260
+ export async function getOAuthToken(
261
+ userId: string,
262
+ provider: 'google' | 'facebook' | 'apple' | 'github'
263
+ ): Promise<OAuthToken | null> {
264
+ const client = await db_init();
265
+
266
+ const tokens = await db_query(client,
267
+ "SELECT * FROM oauthtokens WHERE userid = ? AND provider = ?",
268
+ [userId, provider]
269
+ );
270
+
271
+ return tokens.length > 0 ? tokens[0] as OAuthToken : null;
272
+ }
273
+
274
+ /**
275
+ * Validate session and check for suspicious activity
276
+ */
277
+ export async function validateSession(sessionToken: string): Promise<{
278
+ valid: boolean;
279
+ user?: User;
280
+ needsRotation?: boolean;
281
+ }> {
282
+ const client = await db_init();
283
+
284
+ // Get session details
285
+ const sessions = await db_query(client,
286
+ `SELECT s.*, u.* FROM usersession s
287
+ JOIN users u ON s.userid = u.userid
288
+ WHERE s.sessiontoken = ? AND s.expirationdate::TIMESTAMP > CURRENT_TIMESTAMP AND u.isactive = ?`,
289
+ [sessionToken, 1] // Use 1 instead of true for PostgreSQL compatibility
290
+ );
291
+
292
+ if (!sessions || sessions.length === 0) {
293
+ return { valid: false };
294
+ }
295
+
296
+ const session = sessions[0];
297
+
298
+ // Check if session needs rotation (older than 24 hours)
299
+ const sessionAge = Date.now() - new Date(session.id).getTime();
300
+ const needsRotation = sessionAge > 24 * 60 * 60 * 1000; // 24 hours
301
+
302
+ return {
303
+ valid: true,
304
+ user: {
305
+ userid: session.userid,
306
+ oauthid: session.oauthid,
307
+ source: session.source,
308
+ username: session.username,
309
+ email: session.email,
310
+ avatarurl: session.avatarurl,
311
+ userlevel: session.userlevel,
312
+ lastlogindate: session.lastlogindate,
313
+ createddate: session.createddate,
314
+ isactive: session.isactive
315
+ } as User,
316
+ needsRotation
317
+ };
318
+ }
319
+
320
+ /**
321
+ * Rotate session token for security
322
+ */
323
+ export async function rotateSession(oldSessionToken: string): Promise<string | null> {
324
+ const client = await db_init();
325
+
326
+ // Get existing session
327
+ const sessions = await db_query(client,
328
+ "SELECT * FROM usersession WHERE sessiontoken = ? AND expirationdate::TIMESTAMP > CURRENT_TIMESTAMP",
329
+ [oldSessionToken]
330
+ );
331
+
332
+ if (!sessions || sessions.length === 0) {
333
+ return null;
334
+ }
335
+
336
+ const session = sessions[0];
337
+ const newSessionToken = crypto.randomBytes(32).toString('base64url');
338
+
339
+ // Update session with new token
340
+ await db_query(client,
341
+ "UPDATE usersession SET sessiontoken = ?, expirationdate = ? WHERE id = ?",
342
+ [newSessionToken, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), session.id]
343
+ );
344
+
345
+ return newSessionToken;
346
+ }
347
+
348
+ /**
349
+ * OAuth token refresh endpoints
350
+ */
351
+ const OAUTH_REFRESH_ENDPOINTS = {
352
+ google: 'https://oauth2.googleapis.com/token',
353
+ facebook: 'https://graph.facebook.com/v12.0/oauth/access_token',
354
+ apple: 'https://appleid.apple.com/auth/token',
355
+ github: 'https://github.com/login/oauth/access_token'
356
+ };
357
+
358
+ /**
359
+ * Validate OAuth token with provider
360
+ */
361
+ export async function validateOAuthToken(
362
+ userId: string,
363
+ provider: 'google' | 'facebook' | 'apple' | 'github'
364
+ ): Promise<boolean> {
365
+ const client = await db_init();
366
+
367
+ // Get stored OAuth token
368
+ const token = await getOAuthToken(userId, provider);
369
+ if (!token) {
370
+ return false;
371
+ }
372
+
373
+ // Check if token is expired based on stored expiry
374
+ if (token.expiresat && new Date(token.expiresat) < new Date()) {
375
+ // Try to refresh the token
376
+ if (token.refreshtoken) {
377
+ return await refreshOAuthToken(userId, provider);
378
+ }
379
+ return false;
380
+ }
381
+
382
+ // For providers that don't provide expiry, validate with a test request
383
+ try {
384
+ let validationUrl: string;
385
+ switch (provider) {
386
+ case 'google':
387
+ validationUrl = `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${token.accesstoken}`;
388
+ break;
389
+ case 'facebook':
390
+ validationUrl = `https://graph.facebook.com/me?access_token=${token.accesstoken}`;
391
+ break;
392
+ case 'github':
393
+ validationUrl = 'https://api.github.com/user';
394
+ break;
395
+ default:
396
+ return true; // Skip validation for providers without easy validation endpoints
397
+ }
398
+
399
+ const response = await fetch(validationUrl, {
400
+ headers: provider === 'github' ? {
401
+ Authorization: `Bearer ${token.accesstoken}`
402
+ } : {}
403
+ });
404
+
405
+ return response.ok;
406
+ } catch (error) {
407
+ return false;
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Refresh OAuth token
413
+ */
414
+ export async function refreshOAuthToken(
415
+ userId: string,
416
+ provider: 'google' | 'facebook' | 'apple' | 'github'
417
+ ): Promise<boolean> {
418
+ const client = await db_init();
419
+
420
+ // Get stored OAuth token with refresh token
421
+ const token = await getOAuthToken(userId, provider);
422
+ if (!token || !token.refreshtoken) {
423
+ return false;
424
+ }
425
+
426
+ try {
427
+ const refreshEndpoint = OAUTH_REFRESH_ENDPOINTS[provider];
428
+
429
+ const params: Record<string, string> = {
430
+ grant_type: 'refresh_token',
431
+ refresh_token: token.refreshtoken,
432
+ client_id: process.env[`NEXT_PUBLIC_${provider.toUpperCase()}_CLIENT_ID`]!,
433
+ client_secret: process.env[`${provider.toUpperCase()}_CLIENT_SECRET`]!
434
+ };
435
+
436
+ const response = await fetch(refreshEndpoint, {
437
+ method: 'POST',
438
+ headers: {
439
+ 'Content-Type': 'application/x-www-form-urlencoded',
440
+ Accept: 'application/json'
441
+ },
442
+ body: new URLSearchParams(params).toString()
443
+ });
444
+
445
+ if (!response.ok) {
446
+ return false;
447
+ }
448
+
449
+ const data = await response.json();
450
+
451
+ // Update stored token
452
+ await storeOAuthToken(
453
+ userId,
454
+ provider,
455
+ data.access_token,
456
+ data.refresh_token || token.refreshtoken, // Some providers don't return new refresh token
457
+ data.expires_in
458
+ );
459
+
460
+ return true;
461
+ } catch (error) {
462
+ return false;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Create a secure authentication middleware function
468
+ */
469
+ export async function createAuthMiddleware(): Promise<{
470
+ validateRequest: (sessionToken: string, userAgent?: string, ipAddress?: string) => Promise<{
471
+ valid: boolean;
472
+ user?: User;
473
+ newSessionToken?: string;
474
+ }>;
475
+ }> {
476
+ // Track suspicious activity
477
+ const suspiciousActivity = new Map<string, number>();
478
+
479
+ return {
480
+ validateRequest: async (sessionToken: string, userAgent?: string, ipAddress?: string) => {
481
+ // Check for suspicious activity (too many requests from same IP)
482
+ if (ipAddress) {
483
+ const requestCount = suspiciousActivity.get(ipAddress) || 0;
484
+ if (requestCount > 100) { // 100 requests per minute threshold
485
+ return { valid: false };
486
+ }
487
+ suspiciousActivity.set(ipAddress, requestCount + 1);
488
+ }
489
+
490
+ // Validate session
491
+ const sessionData = await validateSession(sessionToken);
492
+
493
+ if (!sessionData.valid || !sessionData.user) {
494
+ return { valid: false };
495
+ }
496
+
497
+ // Return validated session with potential rotation
498
+ if (sessionData.needsRotation) {
499
+ const newToken = await rotateSession(sessionToken);
500
+ return {
501
+ valid: true,
502
+ user: sessionData.user,
503
+ newSessionToken: newToken || undefined
504
+ };
505
+ }
506
+
507
+ return {
508
+ valid: true,
509
+ user: sessionData.user
510
+ };
511
+ }
512
+ };
513
+ }
514
+
515
+ // Clear suspicious activity counter every minute
516
+ if (typeof setInterval !== 'undefined') {
517
+ setInterval(() => {
518
+ const suspiciousActivity = new Map<string, number>();
519
+ }, 60 * 1000);
520
+ }
@@ -0,0 +1,149 @@
1
+ /*
2
+ Sitepaige v1.0.0
3
+ Database abstraction layer for multiple database support
4
+ WARNING: This file is automatically generated and should not be modified.
5
+ */
6
+
7
+ import * as path from 'path';
8
+ import * as fs from 'fs';
9
+
10
+ interface ModelField {
11
+ name: string;
12
+ datatype: string;
13
+ datatypesize: string | null;
14
+ required: string;
15
+ key: string | null;
16
+ default?: any;
17
+ }
18
+
19
+ interface Model {
20
+ id: string;
21
+ name: string;
22
+ fields: ModelField[];
23
+ data_is_user_specific?: string; // Added for user-specific data
24
+ }
25
+
26
+ // Database client type that's compatible across all database types
27
+ export type DatabaseClient = any;
28
+
29
+ // Database type enum
30
+ export type DatabaseType = 'sqlite' | 'postgres' | 'mysql';
31
+
32
+ // Database configuration
33
+ export interface DatabaseConfig {
34
+ type: DatabaseType;
35
+ connectionString?: string; // For postgres/mysql
36
+ host?: string;
37
+ port?: number;
38
+ user?: string;
39
+ password?: string;
40
+ database?: string;
41
+ sqliteDir?: string; // For sqlite
42
+ efsMountPath?: string; // For production sqlite
43
+ ssl?: {
44
+ rejectUnauthorized?: boolean;
45
+ require?: boolean;
46
+ ca?: string;
47
+ cert?: string;
48
+ key?: string;
49
+ }; // SSL configuration for postgres/mysql
50
+ }
51
+
52
+ // Get database configuration from environment
53
+ export function getDatabaseConfig(): DatabaseConfig {
54
+ const dbType = (process.env.DATABASE_TYPE || 'postgres').toLowerCase() as DatabaseType;
55
+
56
+ switch (dbType) {
57
+ case 'postgres':
58
+ return {
59
+ type: 'postgres',
60
+ connectionString: process.env.DATABASE_URL || process.env.POSTGRES_URL,
61
+ host: process.env.DB_HOST || process.env.POSTGRES_HOST || 'localhost',
62
+ port: parseInt(process.env.DB_PORT || process.env.POSTGRES_PORT || '5432'),
63
+ user: process.env.DB_USER || process.env.POSTGRES_USER || 'postgres',
64
+ password: process.env.DB_PASSWORD || process.env.POSTGRES_PASSWORD,
65
+ database: process.env.DB_NAME || process.env.POSTGRES_DB || 'app',
66
+ ssl: {
67
+ rejectUnauthorized: true,
68
+ require: true // Always require SSL for PostgreSQL
69
+ }
70
+ };
71
+
72
+ case 'mysql':
73
+ return {
74
+ type: 'mysql',
75
+ connectionString: process.env.DATABASE_URL || process.env.MYSQL_URL,
76
+ host: process.env.DB_HOST || process.env.MYSQL_HOST || 'localhost',
77
+ port: parseInt(process.env.DB_PORT || process.env.MYSQL_PORT || '3306'),
78
+ user: process.env.DB_USER || process.env.MYSQL_USER || 'root',
79
+ password: process.env.DB_PASSWORD || process.env.MYSQL_PASSWORD,
80
+ database: process.env.DB_NAME || process.env.MYSQL_DATABASE || 'app'
81
+ };
82
+
83
+ default: // sqlite
84
+ return {
85
+ type: 'sqlite',
86
+ connectionString: process.env.DATABASE_URL, // Add support for DATABASE_URL
87
+ sqliteDir: process.env.SQLITE_DIR || '.',
88
+ efsMountPath: process.env.EFS_MOUNT_PATH
89
+ };
90
+ }
91
+ }
92
+
93
+ // Dynamic imports based on database type
94
+ let dbImplementation: any;
95
+
96
+ async function loadDatabaseImplementation(dbType: DatabaseType) {
97
+ switch (dbType) {
98
+ case 'postgres':
99
+ dbImplementation = await import('./db-postgres');
100
+ break;
101
+ case 'mysql':
102
+ dbImplementation = await import('./db-mysql');
103
+ break;
104
+ default: // sqlite
105
+ dbImplementation = await import('./db-sqlite');
106
+ break;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Initialize database connection
112
+ * @returns Database client
113
+ */
114
+ export async function db_init(): Promise<DatabaseClient> {
115
+ const dbType = (process.env.DATABASE_TYPE || process.env.DB_TYPE || 'postgres').toLowerCase() as DatabaseType;
116
+
117
+ if (!dbImplementation) {
118
+ await loadDatabaseImplementation(dbType);
119
+ }
120
+
121
+ return dbImplementation.db_init();
122
+ }
123
+
124
+ /**
125
+ * Execute a SQL query using the provided database client
126
+ * @param client Database client from db_init()
127
+ * @param query SQL query string
128
+ * @param params Optional array of parameters for the query
129
+ * @returns Array of selected rows or execution results
130
+ */
131
+ export async function db_query(
132
+ client: DatabaseClient,
133
+ query: string,
134
+ params?: any[]
135
+ ): Promise<any[]> {
136
+ return dbImplementation.db_query(client, query, params);
137
+ }
138
+
139
+ /**
140
+ * Generates a CREATE TABLE SQL string for the specified table and fields
141
+ * @param model The model definition
142
+ * @returns SQL string for creating the table
143
+ */
144
+ export function db_migrate(model: Model): string {
145
+ const dbType = (process.env.DATABASE_TYPE || process.env.DB_TYPE || 'postgres').toLowerCase() as DatabaseType;
146
+ return dbImplementation.db_migrate(model, dbType);
147
+ }
148
+ // Export types
149
+ export type { Model, ModelField };