better-auth-studio 1.0.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 (72) hide show
  1. package/README.md +1 -0
  2. package/dist/auth-adapter.d.ts +24 -0
  3. package/dist/auth-adapter.d.ts.map +1 -0
  4. package/dist/auth-adapter.js +481 -0
  5. package/dist/auth-adapter.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +49 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +25 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +308 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/data.d.ts +38 -0
  15. package/dist/data.d.ts.map +1 -0
  16. package/dist/data.js +275 -0
  17. package/dist/data.js.map +1 -0
  18. package/dist/routes.d.ts +3 -0
  19. package/dist/routes.d.ts.map +1 -0
  20. package/dist/routes.js +1490 -0
  21. package/dist/routes.js.map +1 -0
  22. package/dist/studio.d.ts +10 -0
  23. package/dist/studio.d.ts.map +1 -0
  24. package/dist/studio.js +70 -0
  25. package/dist/studio.js.map +1 -0
  26. package/frontend/index.html +13 -0
  27. package/frontend/package-lock.json +4675 -0
  28. package/frontend/package.json +52 -0
  29. package/frontend/pnpm-lock.yaml +4020 -0
  30. package/frontend/postcss.config.js +6 -0
  31. package/frontend/src/App.tsx +36 -0
  32. package/frontend/src/components/CommandPalette.tsx +219 -0
  33. package/frontend/src/components/Layout.tsx +159 -0
  34. package/frontend/src/components/ui/badge.tsx +40 -0
  35. package/frontend/src/components/ui/button.tsx +53 -0
  36. package/frontend/src/components/ui/card.tsx +78 -0
  37. package/frontend/src/components/ui/input.tsx +20 -0
  38. package/frontend/src/components/ui/label.tsx +19 -0
  39. package/frontend/src/components/ui/select.tsx +71 -0
  40. package/frontend/src/index.css +130 -0
  41. package/frontend/src/lib/utils.ts +6 -0
  42. package/frontend/src/main.tsx +10 -0
  43. package/frontend/src/pages/Dashboard.tsx +231 -0
  44. package/frontend/src/pages/OrganizationDetails.tsx +1281 -0
  45. package/frontend/src/pages/Organizations.tsx +874 -0
  46. package/frontend/src/pages/Sessions.tsx +623 -0
  47. package/frontend/src/pages/Settings.tsx +1019 -0
  48. package/frontend/src/pages/TeamDetails.tsx +666 -0
  49. package/frontend/src/pages/Users.tsx +728 -0
  50. package/frontend/tailwind.config.js +75 -0
  51. package/frontend/tsconfig.json +31 -0
  52. package/frontend/tsconfig.node.json +10 -0
  53. package/frontend/vite.config.ts +31 -0
  54. package/package.json +59 -0
  55. package/public/assets/main-C-TXCXVW.css +1 -0
  56. package/public/assets/main-CCzTTP3P.js +296 -0
  57. package/public/index.html +14 -0
  58. package/src/auth-adapter.ts +471 -0
  59. package/src/cli.ts +51 -0
  60. package/src/config.ts +318 -0
  61. package/src/data.ts +351 -0
  62. package/src/routes.ts +1585 -0
  63. package/src/studio.ts +86 -0
  64. package/test-project/README.md +0 -0
  65. package/test-project/better-auth.db +0 -0
  66. package/test-project/better-auth_migrations/2025-08-27T15-55-04.099Z.sql +7 -0
  67. package/test-project/better-auth_migrations/2025-09-04T02-33-19.422Z.sql +7 -0
  68. package/test-project/package.json +29 -0
  69. package/test-project/pnpm-lock.yaml +1728 -0
  70. package/test-project/src/auth.ts +47 -0
  71. package/test-project/src/index.ts +40 -0
  72. package/tsconfig.json +21 -0
package/src/config.ts ADDED
@@ -0,0 +1,318 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+
4
+ export interface AuthProvider {
5
+ type: string;
6
+ clientId?: string;
7
+ clientSecret?: string;
8
+ redirectUri?: string;
9
+ [key: string]: any;
10
+ }
11
+
12
+ export interface AuthDatabase {
13
+ url?: string;
14
+ type?: string;
15
+ dialect?: string;
16
+ [key: string]: any;
17
+ }
18
+
19
+ export interface AuthConfig {
20
+ database?: AuthDatabase;
21
+ providers?: AuthProvider[];
22
+ socialProviders?: Record<string, any>;
23
+ emailAndPassword?: any;
24
+ session?: any;
25
+ secret?: string;
26
+ rateLimit?: any;
27
+ [key: string]: any;
28
+ }
29
+
30
+ export async function findAuthConfig(): Promise<AuthConfig | null> {
31
+ const possibleConfigFiles = [
32
+ 'studio-config.json',
33
+ 'auth.ts',
34
+ 'auth.js',
35
+ 'src/auth.ts',
36
+ 'src/auth.js',
37
+ 'better-auth.config.ts',
38
+ 'better-auth.config.js',
39
+ 'better-auth.config.json',
40
+ 'auth.config.ts',
41
+ 'auth.config.js',
42
+ 'auth.config.json'
43
+ ];
44
+
45
+ let currentDir = process.cwd();
46
+ const maxDepth = 10;
47
+ let depth = 0;
48
+
49
+ while (currentDir && depth < maxDepth) {
50
+ for (const configFile of possibleConfigFiles) {
51
+ const configPath = join(currentDir, configFile);
52
+
53
+ if (existsSync(configPath)) {
54
+ try {
55
+ const config = await loadConfig(configPath);
56
+ if (config) {
57
+ return config;
58
+ }
59
+ } catch (error) {
60
+ console.warn(`Failed to load config from ${configPath}:`, error);
61
+ }
62
+ }
63
+ }
64
+
65
+ const parentDir = dirname(currentDir);
66
+ if (parentDir === currentDir) {
67
+ break;
68
+ }
69
+ currentDir = parentDir;
70
+ depth++;
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ async function loadConfig(configPath: string): Promise<AuthConfig | null> {
77
+ const ext = configPath.split('.').pop();
78
+
79
+ try {
80
+ if (ext === 'json') {
81
+ const content = readFileSync(configPath, 'utf-8');
82
+ return JSON.parse(content);
83
+ } else if (ext === 'js' || ext === 'ts') {
84
+ return await loadTypeScriptConfig(configPath);
85
+ }
86
+ } catch (error) {
87
+ console.warn(`Error loading config from ${configPath}:`, error);
88
+ }
89
+
90
+ return null;
91
+ }
92
+
93
+ async function loadTypeScriptConfig(configPath: string): Promise<AuthConfig | null> {
94
+ try {
95
+ if (configPath.endsWith('.ts')) {
96
+ try {
97
+ const authModule = await import(configPath);
98
+
99
+ if (authModule.auth) {
100
+ console.log('Found auth export, extracting configuration...');
101
+ const config = authModule.auth.options || authModule.auth;
102
+ return extractBetterAuthFields(config);
103
+ } else if (authModule.default) {
104
+ console.log('Found default export, extracting configuration...');
105
+ const config = authModule.default.options || authModule.default;
106
+ return extractBetterAuthFields(config);
107
+ }
108
+ } catch (importError) {
109
+ console.warn(`Failed to import auth config from ${configPath}:`, importError);
110
+ console.log('Falling back to regex extraction...');
111
+ }
112
+ }
113
+
114
+ const content = readFileSync(configPath, 'utf-8');
115
+ const authConfig = extractBetterAuthConfig(content);
116
+ if (authConfig) {
117
+ return authConfig;
118
+ }
119
+
120
+ if (configPath.endsWith('.js')) {
121
+ return await evaluateJSConfig(configPath);
122
+ }
123
+
124
+ return null;
125
+ } catch (error) {
126
+ console.warn(`Error loading TypeScript config from ${configPath}:`, error);
127
+ return null;
128
+ }
129
+ }
130
+
131
+ function extractBetterAuthConfig(content: string): AuthConfig | null {
132
+ console.log('Extracting config from content:', content.substring(0, 500) + '...');
133
+
134
+ const patterns = [
135
+ /export\s+const\s+\w+\s*=\s*betterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
136
+ /export\s+const\s+\w+\s*=\s*BetterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
137
+ /const\s+\w+\s*=\s*betterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
138
+ /const\s+\w+\s*=\s*BetterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
139
+ /export\s+default\s+betterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
140
+ /export\s+default\s+BetterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
141
+ /module\.exports\s*=\s*betterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
142
+ /module\.exports\s*=\s*BetterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
143
+ /export\s+default\s*({[\s\S]*?});?$/m,
144
+ /module\.exports\s*=\s*({[\s\S]*?});?$/m,
145
+ /betterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
146
+ /BetterAuth\s*\(\s*({[\s\S]*?})\s*\)/,
147
+ /({[\s\S]*?"socialProviders"[\s\S]*?})/,
148
+ /({[\s\S]*?"emailAndPassword"[\s\S]*?})/,
149
+ /({[\s\S]*?"database"[\s\S]*?})/
150
+ ];
151
+
152
+ for (let i = 0; i < patterns.length; i++) {
153
+ const pattern = patterns[i];
154
+ const match = content.match(pattern);
155
+ if (match) {
156
+ console.log(`Pattern ${i + 1} matched!`);
157
+ console.log('Matched content:', match[1].substring(0, 200) + '...');
158
+ try {
159
+ let configStr = match[1];
160
+
161
+ configStr = configStr
162
+ .replace(/(\d+\s*\*\s*\d+\s*\*\s*\d+\s*\*\s*\d+)/g, (match) => {
163
+ try {
164
+ return eval(match).toString();
165
+ } catch {
166
+ return match;
167
+ }
168
+ })
169
+ .replace(/(\d+\s*\*\s*\d+\s*\*\s*\d+)/g, (match) => {
170
+ try {
171
+ return eval(match).toString();
172
+ } catch {
173
+ return match;
174
+ }
175
+ })
176
+ .replace(/(\d+\s*\*\s*\d+)/g, (match) => {
177
+ try {
178
+ return eval(match).toString();
179
+ } catch {
180
+ return match;
181
+ }
182
+ });
183
+
184
+ configStr = configStr
185
+ .replace(/:\s*process\.env\.(\w+)(\s*\|\|\s*"[^"]*")?/g, ':"$1"') // Replace process.env.VAR || "default" with "VAR"
186
+ .replace(/:\s*`([^`]*)`/g, ':"$1"') // Replace template literals
187
+ .replace(/:\s*'([^']*)'/g, ':"$1"') // Replace single quotes
188
+ .replace(/:\s*"([^"]*)"/g, ':"$1"') // Keep double quotes
189
+ .replace(/:\s*(\w+)/g, ':"$1"') // Replace unquoted keys
190
+ .replace(/(\w+):/g, '"$1":') // Quote property names
191
+ .replace(/,\s*}/g, '}') // Remove trailing commas
192
+ .replace(/,\s*]/g, ']') // Remove trailing commas in arrays
193
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
194
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
195
+ .replace(/\s+/g, ' ') // Normalize whitespace
196
+ .replace(/:\s*(\d+)/g, ':$1') // Keep numbers
197
+ .replace(/:\s*(true|false)/g, ':$1') // Keep booleans
198
+ .replace(/:\s*null/g, ':null') // Keep null
199
+ .trim();
200
+
201
+ console.log('Cleaned config string:', configStr.substring(0, 300) + '...');
202
+
203
+ let config;
204
+ try {
205
+ config = JSON.parse(configStr);
206
+ } catch (error) {
207
+ console.warn(`Failed to parse config: ${error instanceof Error ? error.message : 'Unknown error'}`);
208
+ console.warn('Config string that failed:', configStr.substring(0, 200) + '...');
209
+ return null;
210
+ }
211
+
212
+ return extractBetterAuthFields(config);
213
+ } catch (error) {
214
+ console.warn(`Failed to parse config pattern: ${error instanceof Error ? error.message : 'Unknown error'}`);
215
+ continue;
216
+ }
217
+ }
218
+ }
219
+
220
+ return null;
221
+ }
222
+
223
+ function extractBetterAuthFields(config: any): AuthConfig {
224
+ console.log('Extracting fields from config:', JSON.stringify(config, null, 2));
225
+
226
+ const authConfig: AuthConfig = {};
227
+
228
+ if (config.database) {
229
+ let dbType = 'postgresql'; // default
230
+ let dbName = config.database.name;
231
+
232
+ if (config.database.constructor && config.database.constructor.name === 'Database') {
233
+ dbType = 'sqlite';
234
+ dbName = config.database.name || './better-auth.db';
235
+ } else if (config.database.name && config.database.name.endsWith('.db')) {
236
+ dbType = 'sqlite';
237
+ } else if (config.database.type) {
238
+ dbType = config.database.type;
239
+ } else if (config.database.dialect) {
240
+ dbType = config.database.dialect;
241
+ }
242
+
243
+ authConfig.database = {
244
+ url: config.database.url || config.database.connectionString,
245
+ name: dbName,
246
+ type: dbType,
247
+ dialect: config.database.dialect,
248
+ casing: config.database.casing
249
+ };
250
+ }
251
+
252
+ if (config.socialProviders) {
253
+ if (typeof config.socialProviders === 'object' && !Array.isArray(config.socialProviders)) {
254
+ authConfig.socialProviders = config.socialProviders;
255
+ authConfig.providers = Object.entries(config.socialProviders).map(([provider, config]: [string, any]) => ({
256
+ type: provider,
257
+ clientId: config.clientId,
258
+ clientSecret: config.clientSecret,
259
+ redirectUri: config.redirectUri,
260
+ ...config
261
+ }));
262
+ } else if (Array.isArray(config.socialProviders)) {
263
+ authConfig.socialProviders = config.socialProviders;
264
+ authConfig.providers = config.socialProviders;
265
+ }
266
+ }
267
+
268
+ if (config.providers && Array.isArray(config.providers)) {
269
+ authConfig.providers = config.providers.map((provider: any) => ({
270
+ type: provider.type || provider.id,
271
+ clientId: provider.clientId || provider.client_id,
272
+ clientSecret: provider.clientSecret || provider.client_secret,
273
+ ...provider
274
+ }));
275
+ }
276
+
277
+ if (config.emailAndPassword) {
278
+ authConfig.emailAndPassword = config.emailAndPassword;
279
+ }
280
+
281
+ if (config.session) {
282
+ authConfig.session = config.session;
283
+ }
284
+
285
+ if (config.secret) {
286
+ authConfig.secret = config.secret;
287
+ }
288
+
289
+ if (config.rateLimit) {
290
+ authConfig.rateLimit = config.rateLimit;
291
+ }
292
+
293
+ if (config.telemetry) {
294
+ authConfig.telemetry = config.telemetry;
295
+ }
296
+ if(config.plugins) {
297
+ authConfig.plugins = config.plugins.map((plugin: any) => plugin.id);
298
+ }
299
+ console.log('Extracted auth config:', JSON.stringify(authConfig, null, 2));
300
+ return authConfig;
301
+ }
302
+
303
+ async function evaluateJSConfig(configPath: string): Promise<AuthConfig | null> {
304
+ try {
305
+ const config = require(configPath);
306
+
307
+ if (config.default) {
308
+ return extractBetterAuthFields(config.default);
309
+ } else if (typeof config === 'object') {
310
+ return extractBetterAuthFields(config);
311
+ }
312
+
313
+ return null;
314
+ } catch (error) {
315
+ console.warn(`Error evaluating JS config from ${configPath}:`, error);
316
+ return null;
317
+ }
318
+ }
package/src/data.ts ADDED
@@ -0,0 +1,351 @@
1
+ import { AuthConfig } from './config';
2
+ import { getAuthAdapter } from './auth-adapter';
3
+
4
+ export interface User {
5
+ id: string;
6
+ email?: string;
7
+ name?: string;
8
+ image?: string;
9
+ emailVerified?: Date;
10
+ createdAt: Date;
11
+ updatedAt: Date;
12
+ provider?: string;
13
+ lastSignIn?: Date;
14
+ }
15
+
16
+ export interface Session {
17
+ id: string;
18
+ userId: string;
19
+ expires: Date;
20
+ createdAt: Date;
21
+ userAgent?: string;
22
+ ip?: string;
23
+ }
24
+
25
+ export interface AuthStats {
26
+ totalUsers: number;
27
+ activeUsers: number;
28
+ totalSessions: number;
29
+ activeSessions: number;
30
+ usersByProvider: Record<string, number>;
31
+ recentSignups: User[];
32
+ recentLogins: Session[];
33
+ }
34
+
35
+ export interface PaginatedResult<T> {
36
+ data: T[];
37
+ total: number;
38
+ page: number;
39
+ limit: number;
40
+ totalPages: number;
41
+ }
42
+
43
+ export async function getAuthData(
44
+ authConfig: AuthConfig,
45
+ type: 'stats' | 'users' | 'sessions' | 'providers' | 'deleteUser' | 'updateUser' = 'stats',
46
+ options?: any
47
+ ): Promise<any> {
48
+ try {
49
+ const adapter = await getAuthAdapter();
50
+
51
+ if (!adapter) {
52
+ console.log('No adapter available, falling back to mock data');
53
+ return getMockData(type, options);
54
+ }
55
+
56
+ switch (type) {
57
+ case 'stats':
58
+ return await getRealStats(adapter);
59
+ case 'users':
60
+ return await getRealUsers(adapter, options);
61
+ case 'sessions':
62
+ return await getRealSessions(adapter, options);
63
+ case 'providers':
64
+ return await getRealProviderStats(adapter);
65
+ case 'deleteUser':
66
+ return await deleteRealUser(adapter, options.id);
67
+ case 'updateUser':
68
+ console.log({adapter})
69
+ return await updateRealUser(adapter, options.id, options.userData);
70
+ default:
71
+ throw new Error(`Unknown data type: ${type}`);
72
+ }
73
+ } catch (error) {
74
+ console.error(`Error fetching ${type} data:`, error);
75
+ return getMockData(type, options);
76
+ }
77
+ }
78
+
79
+ async function getRealStats(adapter: any): Promise<AuthStats> {
80
+ try {
81
+ const users = adapter.getUsers ? await adapter.getUsers() : [];
82
+ const sessions = adapter.getSessions ? await adapter.getSessions() : [];
83
+
84
+ const now = new Date();
85
+ const activeSessions = sessions.filter((s: any) => new Date(s.expiresAt || s.expires) > now);
86
+ const activeUsers = new Set(activeSessions.map((s: any) => s.userId)).size;
87
+
88
+ const usersByProvider: Record<string, number> = {
89
+ email: users.length,
90
+ github: 0
91
+ };
92
+
93
+ const recentSignups = users
94
+ .sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
95
+ .slice(0, 5)
96
+ .map((user: any) => ({
97
+ ...user,
98
+ provider: 'email'
99
+ }));
100
+
101
+ const recentLogins = activeSessions
102
+ .sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
103
+ .slice(0, 5);
104
+
105
+ return {
106
+ totalUsers: users.length,
107
+ activeUsers,
108
+ totalSessions: sessions.length,
109
+ activeSessions: activeSessions.length,
110
+ usersByProvider,
111
+ recentSignups,
112
+ recentLogins
113
+ };
114
+ } catch (error) {
115
+ console.error('Error fetching stats from adapter:', error);
116
+ return getMockData('stats');
117
+ }
118
+ }
119
+
120
+ async function getRealUsers(adapter: any, options: { page: number; limit: number; search?: string }): Promise<PaginatedResult<User>> {
121
+ const { page, limit, search } = options;
122
+
123
+ try {
124
+ if (adapter.getUsers) {
125
+ const allUsers = await adapter.getUsers();
126
+
127
+ let filteredUsers = allUsers;
128
+ if (search) {
129
+ filteredUsers = allUsers.filter((user: any) =>
130
+ user.email?.toLowerCase().includes(search.toLowerCase()) ||
131
+ user.name?.toLowerCase().includes(search.toLowerCase())
132
+ );
133
+ }
134
+
135
+ const startIndex = (page - 1) * limit;
136
+ const endIndex = startIndex + limit;
137
+ const paginatedUsers = filteredUsers.slice(startIndex, endIndex);
138
+
139
+ return {
140
+ data: paginatedUsers,
141
+ total: filteredUsers.length,
142
+ page,
143
+ limit,
144
+ totalPages: Math.ceil(filteredUsers.length / limit)
145
+ };
146
+ }
147
+
148
+ return getMockData('users', options);
149
+ } catch (error) {
150
+ console.error('Error fetching users from adapter:', error);
151
+ return getMockData('users', options);
152
+ }
153
+ }
154
+
155
+ async function getRealSessions(adapter: any, options: { page: number; limit: number }): Promise<PaginatedResult<Session>> {
156
+ const { page, limit } = options;
157
+
158
+ try {
159
+ if (adapter.getSessions) {
160
+ const allSessions = await adapter.getSessions();
161
+
162
+ const startIndex = (page - 1) * limit;
163
+ const endIndex = startIndex + limit;
164
+ const paginatedSessions = allSessions.slice(startIndex, endIndex);
165
+
166
+ return {
167
+ data: paginatedSessions,
168
+ total: allSessions.length,
169
+ page,
170
+ limit,
171
+ totalPages: Math.ceil(allSessions.length / limit)
172
+ };
173
+ }
174
+
175
+ return getMockData('sessions', options);
176
+ } catch (error) {
177
+ console.error('Error fetching sessions from adapter:', error);
178
+ return getMockData('sessions', options);
179
+ }
180
+ }
181
+
182
+ async function getRealProviderStats(adapter: any) {
183
+ try {
184
+ return [
185
+ { type: 'email', users: 0, active: 0 },
186
+ { type: 'github', users: 0, active: 0 }
187
+ ];
188
+ } catch (error) {
189
+ console.error('Error fetching provider stats from adapter:', error);
190
+ return getMockData('providers');
191
+ }
192
+ }
193
+
194
+ async function deleteRealUser(adapter: any, userId: string): Promise<void> {
195
+ try {
196
+ if (adapter.delete) {
197
+ await adapter.delete({ model: 'user', where: [{ field: 'id', value: userId }] });
198
+ } else {
199
+ console.warn('Delete method not available on adapter');
200
+ }
201
+ } catch (error) {
202
+ console.error('Error deleting user from adapter:', error);
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ async function updateRealUser(adapter: any, userId: string, userData: Partial<User>): Promise<User> {
208
+ console.log({userId, userData})
209
+ try {
210
+ const updatedUser = await adapter.update({
211
+ model: 'user',
212
+ where: [
213
+ {
214
+ field: 'id',
215
+ value: userId
216
+ }
217
+ ],
218
+ update: {...userData}
219
+ });
220
+ console.log({updatedUser})
221
+ return updatedUser;
222
+
223
+ } catch (error) {
224
+ console.error('Error updating user from adapter:', error);
225
+ throw error;
226
+ }
227
+ }
228
+
229
+ function getMockData(type: string, options?: any): any {
230
+ switch (type) {
231
+ case 'stats':
232
+ return getMockStats();
233
+ case 'users':
234
+ return getMockUsers(options);
235
+ case 'sessions':
236
+ return getMockSessions(options);
237
+ case 'providers':
238
+ return getMockProviderStats();
239
+ case 'deleteUser':
240
+ return Promise.resolve();
241
+ case 'updateUser':
242
+ return Promise.resolve(generateMockUsers(1)[0]);
243
+ default:
244
+ throw new Error(`Unknown data type: ${type}`);
245
+ }
246
+ }
247
+
248
+ function getMockStats(): AuthStats {
249
+ return {
250
+ totalUsers: 1247,
251
+ activeUsers: 892,
252
+ totalSessions: 3456,
253
+ activeSessions: 1234,
254
+ usersByProvider: {
255
+ 'google': 456,
256
+ 'github': 234,
257
+ 'email': 557
258
+ },
259
+ recentSignups: generateMockUsers(5),
260
+ recentLogins: generateMockSessions(5)
261
+ };
262
+ }
263
+
264
+ function getMockUsers(options: { page: number; limit: number; search?: string }): PaginatedResult<User> {
265
+ const { page, limit, search } = options;
266
+ const allUsers = generateMockUsers(100);
267
+
268
+ let filteredUsers = allUsers;
269
+ if (search) {
270
+ filteredUsers = allUsers.filter(user =>
271
+ user.email?.toLowerCase().includes(search.toLowerCase()) ||
272
+ user.name?.toLowerCase().includes(search.toLowerCase())
273
+ );
274
+ }
275
+
276
+ const start = (page - 1) * limit;
277
+ const end = start + limit;
278
+ const data = filteredUsers.slice(start, end);
279
+
280
+ return {
281
+ data,
282
+ total: filteredUsers.length,
283
+ page,
284
+ limit,
285
+ totalPages: Math.ceil(filteredUsers.length / limit)
286
+ };
287
+ }
288
+
289
+ function getMockSessions(options: { page: number; limit: number }): PaginatedResult<Session> {
290
+ const { page, limit } = options;
291
+ const allSessions = generateMockSessions(200);
292
+
293
+ const start = (page - 1) * limit;
294
+ const end = start + limit;
295
+ const data = allSessions.slice(start, end);
296
+
297
+ return {
298
+ data,
299
+ total: allSessions.length,
300
+ page,
301
+ limit,
302
+ totalPages: Math.ceil(allSessions.length / limit)
303
+ };
304
+ }
305
+
306
+ function getMockProviderStats() {
307
+ return [
308
+ { type: 'google', users: 456, active: 234 },
309
+ { type: 'github', users: 234, active: 123 },
310
+ { type: 'email', users: 557, active: 345 }
311
+ ];
312
+ }
313
+
314
+ function generateMockUsers(count: number): User[] {
315
+ const users: User[] = [];
316
+ const providers = ['google', 'github', 'email'];
317
+
318
+ for (let i = 0; i < count; i++) {
319
+ const provider = providers[Math.floor(Math.random() * providers.length)];
320
+ users.push({
321
+ id: `user_${i + 1}`,
322
+ email: `user${i + 1}@example.com`,
323
+ name: `User ${i + 1}`,
324
+ image: `https://api.dicebear.com/7.x/avataaars/svg?seed=user${i + 1}`,
325
+ emailVerified: Math.random() > 0.3 ? new Date() : undefined,
326
+ createdAt: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
327
+ updatedAt: new Date(),
328
+ provider,
329
+ lastSignIn: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000)
330
+ });
331
+ }
332
+
333
+ return users;
334
+ }
335
+
336
+ function generateMockSessions(count: number): Session[] {
337
+ const sessions: Session[] = [];
338
+
339
+ for (let i = 0; i < count; i++) {
340
+ sessions.push({
341
+ id: `session_${i + 1}`,
342
+ userId: `user_${Math.floor(Math.random() * 100) + 1}`,
343
+ expires: new Date(Date.now() + Math.random() * 24 * 60 * 60 * 1000),
344
+ createdAt: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000),
345
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
346
+ ip: `192.168.1.${Math.floor(Math.random() * 255)}`
347
+ });
348
+ }
349
+
350
+ return sessions;
351
+ }