@chimerai/cli 0.2.73
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/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +317 -0
- package/dist/commands/add.d.ts +11 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +2126 -0
- package/dist/commands/create.d.ts +12 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +1703 -0
- package/dist/commands/deploy.d.ts +11 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +219 -0
- package/dist/commands/dev.d.ts +17 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +206 -0
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +728 -0
- package/dist/commands/generate.d.ts +19 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +429 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +269 -0
- package/dist/commands/list.d.ts +12 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +328 -0
- package/dist/commands/migrate.d.ts +14 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +197 -0
- package/dist/commands/plugin.d.ts +10 -0
- package/dist/commands/plugin.d.ts.map +1 -0
- package/dist/commands/plugin.js +239 -0
- package/dist/commands/remove.d.ts +11 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +472 -0
- package/dist/commands/secret.d.ts +12 -0
- package/dist/commands/secret.d.ts.map +1 -0
- package/dist/commands/secret.js +102 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +788 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +211 -0
- package/dist/commands/use.d.ts +9 -0
- package/dist/commands/use.d.ts.map +1 -0
- package/dist/commands/use.js +51 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/license.d.ts +55 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +258 -0
- package/dist/scanner.d.ts +31 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +113 -0
- package/dist/schema-manager.d.ts +26 -0
- package/dist/schema-manager.d.ts.map +1 -0
- package/dist/schema-manager.js +132 -0
- package/dist/templates/admin.d.ts +49 -0
- package/dist/templates/admin.d.ts.map +1 -0
- package/dist/templates/admin.js +1358 -0
- package/dist/templates/ai-routes.d.ts +17 -0
- package/dist/templates/ai-routes.d.ts.map +1 -0
- package/dist/templates/ai-routes.js +1130 -0
- package/dist/templates/ai-service-tools.d.ts +22 -0
- package/dist/templates/ai-service-tools.d.ts.map +1 -0
- package/dist/templates/ai-service-tools.js +1424 -0
- package/dist/templates/ai-service.d.ts +66 -0
- package/dist/templates/ai-service.d.ts.map +1 -0
- package/dist/templates/ai-service.js +2202 -0
- package/dist/templates/api-routes.d.ts +108 -0
- package/dist/templates/api-routes.d.ts.map +1 -0
- package/dist/templates/api-routes.js +1219 -0
- package/dist/templates/auth.d.ts +48 -0
- package/dist/templates/auth.d.ts.map +1 -0
- package/dist/templates/auth.js +381 -0
- package/dist/templates/billing.d.ts +44 -0
- package/dist/templates/billing.d.ts.map +1 -0
- package/dist/templates/billing.js +551 -0
- package/dist/templates/chat.d.ts +63 -0
- package/dist/templates/chat.d.ts.map +1 -0
- package/dist/templates/chat.js +1979 -0
- package/dist/templates/components.d.ts +22 -0
- package/dist/templates/components.d.ts.map +1 -0
- package/dist/templates/components.js +672 -0
- package/dist/templates/config.d.ts +6 -0
- package/dist/templates/config.d.ts.map +1 -0
- package/dist/templates/config.js +86 -0
- package/dist/templates/docker.d.ts +25 -0
- package/dist/templates/docker.d.ts.map +1 -0
- package/dist/templates/docker.js +165 -0
- package/dist/templates/gdpr.d.ts +16 -0
- package/dist/templates/gdpr.d.ts.map +1 -0
- package/dist/templates/gdpr.js +259 -0
- package/dist/templates/index.d.ts +77 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +339 -0
- package/dist/templates/layout.d.ts +67 -0
- package/dist/templates/layout.d.ts.map +1 -0
- package/dist/templates/layout.js +670 -0
- package/dist/templates/mfa.d.ts +23 -0
- package/dist/templates/mfa.d.ts.map +1 -0
- package/dist/templates/mfa.js +353 -0
- package/dist/templates/middleware.d.ts +12 -0
- package/dist/templates/middleware.d.ts.map +1 -0
- package/dist/templates/middleware.js +116 -0
- package/dist/templates/prisma.d.ts +35 -0
- package/dist/templates/prisma.d.ts.map +1 -0
- package/dist/templates/prisma.js +724 -0
- package/dist/templates/provider-routes.d.ts +21 -0
- package/dist/templates/provider-routes.d.ts.map +1 -0
- package/dist/templates/provider-routes.js +1203 -0
- package/dist/templates/rag.d.ts +48 -0
- package/dist/templates/rag.d.ts.map +1 -0
- package/dist/templates/rag.js +532 -0
- package/dist/templates/widget.d.ts +64 -0
- package/dist/templates/widget.d.ts.map +1 -0
- package/dist/templates/widget.js +1360 -0
- package/dist/utils/provider-db.d.ts +63 -0
- package/dist/utils/provider-db.d.ts.map +1 -0
- package/dist/utils/provider-db.js +300 -0
- package/dist/utils.d.ts +78 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +330 -0
- package/package.json +60 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Prisma and library templates
|
|
4
|
+
* Generates Prisma client initialization, encryption utilities, and API key auth
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.generatePrismaLib = generatePrismaLib;
|
|
8
|
+
exports.generateEncryptionLib = generateEncryptionLib;
|
|
9
|
+
exports.generateApiKeyAuthLib = generateApiKeyAuthLib;
|
|
10
|
+
exports.generateApiProtectionLib = generateApiProtectionLib;
|
|
11
|
+
exports.generatePrismaSchema = generatePrismaSchema;
|
|
12
|
+
/**
|
|
13
|
+
* Generates the Prisma client singleton
|
|
14
|
+
* Uses global singleton pattern to prevent connection exhaustion in serverless environments
|
|
15
|
+
* @returns TypeScript content for lib/prisma.ts
|
|
16
|
+
*/
|
|
17
|
+
function generatePrismaLib() {
|
|
18
|
+
return `// @chimerai component=PrismaLib version=1.0
|
|
19
|
+
import { PrismaClient } from '@prisma/client';
|
|
20
|
+
|
|
21
|
+
const globalForPrisma = globalThis as unknown as {
|
|
22
|
+
prisma: PrismaClient | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
|
26
|
+
|
|
27
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generates the encryption utility for API key storage
|
|
32
|
+
* Uses AES-256-GCM for authenticated symmetric encryption
|
|
33
|
+
* @returns TypeScript content for lib/encryption.ts
|
|
34
|
+
*/
|
|
35
|
+
function generateEncryptionLib() {
|
|
36
|
+
return `// @chimerai component=EncryptionLib version=1.0
|
|
37
|
+
import crypto from 'crypto';
|
|
38
|
+
|
|
39
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
40
|
+
const IV_LENGTH = 16; // 128 bits — must match CLI and @chimerai/model-providers
|
|
41
|
+
const AUTH_TAG_LENGTH = 16;
|
|
42
|
+
|
|
43
|
+
function getKey(): Buffer {
|
|
44
|
+
const key = process.env.PROVIDER_ENCRYPTION_KEY;
|
|
45
|
+
if (!key) {
|
|
46
|
+
throw new Error('PROVIDER_ENCRYPTION_KEY environment variable is required');
|
|
47
|
+
}
|
|
48
|
+
// If key is hex (64 chars = 32 bytes), use as-is; otherwise hash it
|
|
49
|
+
if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
|
|
50
|
+
return Buffer.from(key, 'hex');
|
|
51
|
+
}
|
|
52
|
+
return crypto.createHash('sha256').update(key).digest();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function encrypt(text: string): string {
|
|
56
|
+
if (!text) return '';
|
|
57
|
+
const key = getKey();
|
|
58
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
59
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
60
|
+
let encrypted = cipher.update(text, 'utf8', 'base64');
|
|
61
|
+
encrypted += cipher.final('base64');
|
|
62
|
+
const authTag = cipher.getAuthTag();
|
|
63
|
+
return iv.toString('base64') + ':' + authTag.toString('base64') + ':' + encrypted;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function decrypt(text: string): string {
|
|
67
|
+
if (!text) return '';
|
|
68
|
+
const key = getKey();
|
|
69
|
+
const parts = text.split(':');
|
|
70
|
+
if (parts.length !== 3) {
|
|
71
|
+
throw new Error('Invalid encrypted text format');
|
|
72
|
+
}
|
|
73
|
+
const iv = Buffer.from(parts[0], 'base64');
|
|
74
|
+
const authTag = Buffer.from(parts[1], 'base64');
|
|
75
|
+
const encryptedText = parts[2];
|
|
76
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
|
|
77
|
+
decipher.setAuthTag(authTag);
|
|
78
|
+
let decrypted = decipher.update(encryptedText, 'base64', 'utf8');
|
|
79
|
+
decrypted += decipher.final('utf8');
|
|
80
|
+
return decrypted;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Encrypt an API key for storage (alias for encrypt)
|
|
85
|
+
*/
|
|
86
|
+
export function encryptApiKey(apiKey: string): string {
|
|
87
|
+
return encrypt(apiKey);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Decrypt an API key from storage (alias for decrypt)
|
|
92
|
+
*/
|
|
93
|
+
export function decryptApiKey(encryptedKey: string): string {
|
|
94
|
+
return decrypt(encryptedKey);
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generates the API key authentication middleware
|
|
100
|
+
* Supports API key-based auth for standalone widgets and external integrations
|
|
101
|
+
* @returns TypeScript content for lib/api-key-auth.ts
|
|
102
|
+
*/
|
|
103
|
+
function generateApiKeyAuthLib() {
|
|
104
|
+
return `// @chimerai component=ApiKeyAuthLib version=1.1
|
|
105
|
+
/**
|
|
106
|
+
* API Key Authentication Middleware for ChimerAI
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
import { NextRequest } from 'next/server';
|
|
110
|
+
import { prisma } from './prisma';
|
|
111
|
+
import { createHash } from 'crypto';
|
|
112
|
+
|
|
113
|
+
export interface ApiKeyResult {
|
|
114
|
+
valid: boolean;
|
|
115
|
+
userId?: string;
|
|
116
|
+
email?: string;
|
|
117
|
+
error?: string;
|
|
118
|
+
keyName?: string;
|
|
119
|
+
scopes?: string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function hashApiKey(apiKey: string): string {
|
|
123
|
+
return createHash('sha256').update(apiKey).digest('hex');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function generateApiKey(): string {
|
|
127
|
+
const randomBytes = createHash('sha256')
|
|
128
|
+
.update(Math.random().toString() + Date.now().toString())
|
|
129
|
+
.digest('hex')
|
|
130
|
+
.slice(0, 32);
|
|
131
|
+
|
|
132
|
+
return \`sk_live_\${randomBytes}\`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Checks if the given scopes include the required scope.
|
|
137
|
+
* Supports wildcards: ['*'] matches everything, ['chat:*'] matches 'chat:send'.
|
|
138
|
+
* Empty scopes array = unrestricted (backward-compatible).
|
|
139
|
+
*/
|
|
140
|
+
export function hasScope(scopes: string[], required: string): boolean {
|
|
141
|
+
if (scopes.length === 0) return true; // No scope = everything allowed
|
|
142
|
+
if (scopes.includes('*')) return true; // Super-wildcard
|
|
143
|
+
if (scopes.includes(required)) return true; // Exact match
|
|
144
|
+
|
|
145
|
+
// Category wildcard: 'chat:*' matches 'chat:send'
|
|
146
|
+
const requiredParts = required.split(':');
|
|
147
|
+
for (const scope of scopes) {
|
|
148
|
+
if (scope.endsWith(':*')) {
|
|
149
|
+
const prefix = scope.slice(0, -1);
|
|
150
|
+
if (required.startsWith(prefix)) return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function verifyApiKey(request: NextRequest): Promise<ApiKeyResult> {
|
|
158
|
+
const authHeader = request.headers.get('authorization');
|
|
159
|
+
const apiKeyHeader = request.headers.get('x-api-key');
|
|
160
|
+
|
|
161
|
+
let apiKey: string | null = null;
|
|
162
|
+
|
|
163
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
164
|
+
apiKey = authHeader.slice(7);
|
|
165
|
+
} else if (apiKeyHeader) {
|
|
166
|
+
apiKey = apiKeyHeader;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!apiKey) {
|
|
170
|
+
return {
|
|
171
|
+
valid: false,
|
|
172
|
+
error: 'No API key provided',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {
|
|
177
|
+
return {
|
|
178
|
+
valid: false,
|
|
179
|
+
error: 'Invalid API key format',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const keyHash = hashApiKey(apiKey);
|
|
185
|
+
const dbKey = await (prisma as any).apiKey.findUnique({
|
|
186
|
+
where: { keyHash },
|
|
187
|
+
include: {
|
|
188
|
+
user: {
|
|
189
|
+
select: {
|
|
190
|
+
id: true,
|
|
191
|
+
email: true,
|
|
192
|
+
name: true,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (!dbKey) {
|
|
199
|
+
return {
|
|
200
|
+
valid: false,
|
|
201
|
+
error: 'Invalid API key',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (dbKey.revoked) {
|
|
206
|
+
return {
|
|
207
|
+
valid: false,
|
|
208
|
+
error: 'API key has been revoked',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check expiry
|
|
213
|
+
if (dbKey.expiresAt && new Date(dbKey.expiresAt) < new Date()) {
|
|
214
|
+
return {
|
|
215
|
+
valid: false,
|
|
216
|
+
error: 'API key has expired',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Update last used timestamp
|
|
221
|
+
await (prisma as any).apiKey.update({
|
|
222
|
+
where: { keyHash },
|
|
223
|
+
data: { lastUsedAt: new Date() },
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Parse scopes: stored as comma-separated string in SQLite
|
|
227
|
+
const rawScopes = dbKey.scopes || '';
|
|
228
|
+
const scopeList = typeof rawScopes === 'string'
|
|
229
|
+
? (rawScopes ? rawScopes.split(',').map((s: string) => s.trim()) : [])
|
|
230
|
+
: rawScopes;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
valid: true,
|
|
234
|
+
userId: dbKey.user.id,
|
|
235
|
+
email: dbKey.user.email || '',
|
|
236
|
+
keyName: dbKey.name,
|
|
237
|
+
scopes: scopeList,
|
|
238
|
+
};
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('API key verification error:', error);
|
|
241
|
+
return {
|
|
242
|
+
valid: false,
|
|
243
|
+
error: 'API key verification failed',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Generates the API protection middleware
|
|
251
|
+
* Handles session/auth checks, permission verification, and usage tracking
|
|
252
|
+
* @returns TypeScript content for lib/api-protection.ts
|
|
253
|
+
*/
|
|
254
|
+
function generateApiProtectionLib() {
|
|
255
|
+
return `// @chimerai component=ApiProtectionLib version=1.0
|
|
256
|
+
/**
|
|
257
|
+
* API Protection & Authorization Utilities
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
261
|
+
import { getServerSession } from 'next-auth';
|
|
262
|
+
import { authOptions } from './auth';
|
|
263
|
+
import { prisma } from './prisma';
|
|
264
|
+
|
|
265
|
+
export interface AuthResult {
|
|
266
|
+
authorized: boolean;
|
|
267
|
+
user?: { id: string; email: string };
|
|
268
|
+
error?: { code: string; message: string; statusCode: number };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function requireAuth(request: NextRequest): Promise<AuthResult> {
|
|
272
|
+
const session = await getServerSession(authOptions);
|
|
273
|
+
|
|
274
|
+
if (!session?.user?.id) {
|
|
275
|
+
return {
|
|
276
|
+
authorized: false,
|
|
277
|
+
error: {
|
|
278
|
+
code: 'UNAUTHORIZED',
|
|
279
|
+
message: 'Authentication required',
|
|
280
|
+
statusCode: 401,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
authorized: true,
|
|
287
|
+
user: {
|
|
288
|
+
id: session.user.id as string,
|
|
289
|
+
email: session.user.email as string,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function requireModelPermission(
|
|
295
|
+
request: NextRequest,
|
|
296
|
+
model: string
|
|
297
|
+
): Promise<AuthResult> {
|
|
298
|
+
const session = await getServerSession(authOptions);
|
|
299
|
+
|
|
300
|
+
if (!session?.user?.id) {
|
|
301
|
+
return {
|
|
302
|
+
authorized: false,
|
|
303
|
+
error: {
|
|
304
|
+
code: 'UNAUTHORIZED',
|
|
305
|
+
message: 'Authentication required',
|
|
306
|
+
statusCode: 401,
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check if user has access to this model
|
|
312
|
+
try {
|
|
313
|
+
const access = await (prisma as any).modelAccess.findFirst({
|
|
314
|
+
where: {
|
|
315
|
+
userId: session.user.id,
|
|
316
|
+
model,
|
|
317
|
+
enabled: true,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (!access) {
|
|
322
|
+
return {
|
|
323
|
+
authorized: false,
|
|
324
|
+
error: {
|
|
325
|
+
code: 'FORBIDDEN',
|
|
326
|
+
message: \`Access to model '\${model}' is not allowed\`,
|
|
327
|
+
statusCode: 403,
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('Permission check error:', error);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
authorized: true,
|
|
337
|
+
user: {
|
|
338
|
+
id: session.user.id as string,
|
|
339
|
+
email: session.user.email as string,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function requireCredits(
|
|
345
|
+
request: NextRequest,
|
|
346
|
+
estimatedTokens: number
|
|
347
|
+
): Promise<AuthResult> {
|
|
348
|
+
const session = await getServerSession(authOptions);
|
|
349
|
+
|
|
350
|
+
if (!session?.user?.id) {
|
|
351
|
+
return {
|
|
352
|
+
authorized: false,
|
|
353
|
+
error: {
|
|
354
|
+
code: 'UNAUTHORIZED',
|
|
355
|
+
message: 'Authentication required',
|
|
356
|
+
statusCode: 401,
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const user = await (prisma as any).user.findUnique({
|
|
363
|
+
where: { id: session.user.id },
|
|
364
|
+
select: { credits: true },
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const requiredCredits = Math.ceil(estimatedTokens / 100);
|
|
368
|
+
|
|
369
|
+
if (!user || user.credits < requiredCredits) {
|
|
370
|
+
return {
|
|
371
|
+
authorized: false,
|
|
372
|
+
error: {
|
|
373
|
+
code: 'INSUFFICIENT_CREDITS',
|
|
374
|
+
message: 'Insufficient credits for this operation',
|
|
375
|
+
statusCode: 402,
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('Credits check error:', error);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
authorized: true,
|
|
385
|
+
user: {
|
|
386
|
+
id: session.user.id as string,
|
|
387
|
+
email: session.user.email as string,
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function createErrorResponse(authResult: AuthResult) {
|
|
393
|
+
return NextResponse.json(
|
|
394
|
+
{
|
|
395
|
+
error: {
|
|
396
|
+
code: authResult.error?.code || 'ERROR',
|
|
397
|
+
message: authResult.error?.message || 'Unknown error',
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
{ status: authResult.error?.statusCode || 500 }
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export async function trackApiUsage(
|
|
405
|
+
userId: string,
|
|
406
|
+
endpoint: string,
|
|
407
|
+
model: string,
|
|
408
|
+
inputTokens: number,
|
|
409
|
+
outputTokens: number,
|
|
410
|
+
success: boolean,
|
|
411
|
+
errorMessage?: string
|
|
412
|
+
) {
|
|
413
|
+
try {
|
|
414
|
+
await (prisma as any).apiUsageLog.create({
|
|
415
|
+
data: {
|
|
416
|
+
userId,
|
|
417
|
+
endpoint,
|
|
418
|
+
model,
|
|
419
|
+
inputTokens,
|
|
420
|
+
outputTokens,
|
|
421
|
+
success,
|
|
422
|
+
errorMessage,
|
|
423
|
+
timestamp: new Date(),
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error('Failed to track API usage:', error);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
`;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Generates the Prisma schema for all features
|
|
434
|
+
* Includes User, Role, Provider, Model, Conversation, Message, AuditLog, and SystemSetting models
|
|
435
|
+
* @returns TypeScript content for prisma/schema.prisma (content)
|
|
436
|
+
*/
|
|
437
|
+
function generatePrismaSchema() {
|
|
438
|
+
return `// @chimerai component=PrismaSchema version=2.0
|
|
439
|
+
generator client {
|
|
440
|
+
provider = "prisma-client-js"
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
datasource db {
|
|
444
|
+
provider = "postgresql"
|
|
445
|
+
url = env("DATABASE_URL")
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
model User {
|
|
449
|
+
id String @id @default(cuid())
|
|
450
|
+
name String?
|
|
451
|
+
email String? @unique
|
|
452
|
+
emailVerified DateTime?
|
|
453
|
+
image String?
|
|
454
|
+
password String?
|
|
455
|
+
createdAt DateTime @default(now())
|
|
456
|
+
updatedAt DateTime @updatedAt
|
|
457
|
+
|
|
458
|
+
accounts Account[]
|
|
459
|
+
sessions Session[]
|
|
460
|
+
roles UserRole[]
|
|
461
|
+
providers Provider[] @relation("CreatedProviders")
|
|
462
|
+
apiUsage ApiUsage[]
|
|
463
|
+
apiKeys ApiKey[]
|
|
464
|
+
modelAccess ModelAccess[]
|
|
465
|
+
auditLogs AuditLog[]
|
|
466
|
+
conversations Conversation[]
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
model Account {
|
|
470
|
+
id String @id @default(cuid())
|
|
471
|
+
userId String
|
|
472
|
+
type String
|
|
473
|
+
provider String
|
|
474
|
+
providerAccountId String
|
|
475
|
+
refresh_token String? @db.Text
|
|
476
|
+
access_token String? @db.Text
|
|
477
|
+
expires_at Int?
|
|
478
|
+
token_type String?
|
|
479
|
+
scope String?
|
|
480
|
+
id_token String? @db.Text
|
|
481
|
+
session_state String?
|
|
482
|
+
|
|
483
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
484
|
+
|
|
485
|
+
@@unique([provider, providerAccountId])
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
model Session {
|
|
489
|
+
id String @id @default(cuid())
|
|
490
|
+
sessionToken String @unique
|
|
491
|
+
userId String
|
|
492
|
+
expires DateTime
|
|
493
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
model VerificationToken {
|
|
497
|
+
identifier String
|
|
498
|
+
token String @unique
|
|
499
|
+
expires DateTime
|
|
500
|
+
|
|
501
|
+
@@unique([identifier, token])
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
model ApiKey {
|
|
505
|
+
id String @id @default(cuid())
|
|
506
|
+
name String
|
|
507
|
+
keyHash String @unique
|
|
508
|
+
userId String
|
|
509
|
+
scopes String[] @default([])
|
|
510
|
+
revoked Boolean @default(false)
|
|
511
|
+
lastUsedAt DateTime?
|
|
512
|
+
expiresAt DateTime?
|
|
513
|
+
createdAt DateTime @default(now())
|
|
514
|
+
updatedAt DateTime @updatedAt
|
|
515
|
+
|
|
516
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
517
|
+
|
|
518
|
+
@@index([userId])
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
model Role {
|
|
522
|
+
id String @id @default(cuid())
|
|
523
|
+
name String @unique
|
|
524
|
+
description String?
|
|
525
|
+
permissions String[]
|
|
526
|
+
createdAt DateTime @default(now())
|
|
527
|
+
updatedAt DateTime @updatedAt
|
|
528
|
+
|
|
529
|
+
users UserRole[]
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
model UserRole {
|
|
533
|
+
userId String
|
|
534
|
+
roleId String
|
|
535
|
+
|
|
536
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
537
|
+
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
538
|
+
|
|
539
|
+
@@id([userId, roleId])
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// === Provider Management (Core Infrastructure) ===
|
|
543
|
+
|
|
544
|
+
model Provider {
|
|
545
|
+
id String @id @default(cuid())
|
|
546
|
+
name String
|
|
547
|
+
type String // "openai", "anthropic", "ollama", "google", "custom"
|
|
548
|
+
description String?
|
|
549
|
+
baseUrl String?
|
|
550
|
+
apiKey String? @db.Text // AES-256-GCM encrypted, null for keyless providers (e.g. Ollama)
|
|
551
|
+
config Json @default("{}")
|
|
552
|
+
status String @default("active")
|
|
553
|
+
isDefault Boolean @default(false)
|
|
554
|
+
priority Int @default(0)
|
|
555
|
+
tags String[]
|
|
556
|
+
createdAt DateTime @default(now())
|
|
557
|
+
updatedAt DateTime @updatedAt
|
|
558
|
+
createdBy String?
|
|
559
|
+
|
|
560
|
+
models Model[]
|
|
561
|
+
health ProviderHealth?
|
|
562
|
+
apiUsage ApiUsage[]
|
|
563
|
+
conversations Conversation[]
|
|
564
|
+
creator User? @relation("CreatedProviders", fields: [createdBy], references: [id])
|
|
565
|
+
|
|
566
|
+
@@index([type])
|
|
567
|
+
@@index([status])
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
model Model {
|
|
571
|
+
id String @id @default(cuid())
|
|
572
|
+
providerId String
|
|
573
|
+
modelId String // e.g. "gpt-4", "claude-3-sonnet"
|
|
574
|
+
name String
|
|
575
|
+
description String?
|
|
576
|
+
capabilities String[] // ["chat", "embedding", "image", "vision"]
|
|
577
|
+
contextWindow Int @default(4096)
|
|
578
|
+
maxOutputTokens Int?
|
|
579
|
+
inputCost Float @default(0) // $ per 1M tokens
|
|
580
|
+
outputCost Float @default(0)
|
|
581
|
+
isAvailable Boolean @default(true)
|
|
582
|
+
isDeprecated Boolean @default(false)
|
|
583
|
+
|
|
584
|
+
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
585
|
+
|
|
586
|
+
@@unique([providerId, modelId])
|
|
587
|
+
@@index([providerId])
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
model ProviderHealth {
|
|
591
|
+
id String @id @default(cuid())
|
|
592
|
+
providerId String @unique
|
|
593
|
+
status String @default("unknown") // "healthy", "degraded", "unhealthy"
|
|
594
|
+
responseTime Int? // ms
|
|
595
|
+
lastCheck DateTime @default(now())
|
|
596
|
+
errorMessage String?
|
|
597
|
+
modelsAvailable Int @default(0)
|
|
598
|
+
chatAvailable Boolean @default(false)
|
|
599
|
+
embeddingAvailable Boolean @default(false)
|
|
600
|
+
apiKeyValid Boolean @default(false)
|
|
601
|
+
|
|
602
|
+
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
model ApiUsage {
|
|
606
|
+
id String @id @default(cuid())
|
|
607
|
+
userId String
|
|
608
|
+
providerId String?
|
|
609
|
+
model String
|
|
610
|
+
endpoint String
|
|
611
|
+
promptTokens Int @default(0)
|
|
612
|
+
completionTokens Int @default(0)
|
|
613
|
+
totalTokens Int @default(0)
|
|
614
|
+
tokensUsed Int @default(0)
|
|
615
|
+
creditsUsed Int @default(0)
|
|
616
|
+
cost Float @default(0)
|
|
617
|
+
success Boolean @default(true)
|
|
618
|
+
errorMessage String?
|
|
619
|
+
responseTime Int @default(0) // ms
|
|
620
|
+
createdAt DateTime @default(now())
|
|
621
|
+
|
|
622
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
623
|
+
provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
624
|
+
|
|
625
|
+
@@index([userId])
|
|
626
|
+
@@index([providerId])
|
|
627
|
+
@@index([createdAt])
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
model ModelAccess {
|
|
631
|
+
id String @id @default(cuid())
|
|
632
|
+
userId String
|
|
633
|
+
modelId String
|
|
634
|
+
granted Boolean @default(true)
|
|
635
|
+
createdAt DateTime @default(now())
|
|
636
|
+
updatedAt DateTime @updatedAt
|
|
637
|
+
|
|
638
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
639
|
+
|
|
640
|
+
@@unique([userId, modelId])
|
|
641
|
+
@@index([userId])
|
|
642
|
+
@@index([modelId])
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
model AuditLog {
|
|
646
|
+
id String @id @default(cuid())
|
|
647
|
+
action String
|
|
648
|
+
userId String
|
|
649
|
+
targetType String?
|
|
650
|
+
targetId String?
|
|
651
|
+
metadata Json?
|
|
652
|
+
ipAddress String?
|
|
653
|
+
createdAt DateTime @default(now())
|
|
654
|
+
|
|
655
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
656
|
+
|
|
657
|
+
@@index([userId])
|
|
658
|
+
@@index([action])
|
|
659
|
+
@@index([createdAt])
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
model PromptTemplate {
|
|
663
|
+
id String @id @default(cuid())
|
|
664
|
+
name String @unique
|
|
665
|
+
category String
|
|
666
|
+
description String?
|
|
667
|
+
content String @db.Text
|
|
668
|
+
variables String[]
|
|
669
|
+
language String @default("en")
|
|
670
|
+
version Int @default(1)
|
|
671
|
+
isActive Boolean @default(true)
|
|
672
|
+
isDefault Boolean @default(false)
|
|
673
|
+
tags String[]
|
|
674
|
+
metadata Json?
|
|
675
|
+
createdBy String?
|
|
676
|
+
createdAt DateTime @default(now())
|
|
677
|
+
updatedAt DateTime @updatedAt
|
|
678
|
+
|
|
679
|
+
@@index([category])
|
|
680
|
+
@@index([isActive])
|
|
681
|
+
@@index([isDefault])
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
model Conversation {
|
|
685
|
+
id String @id @default(cuid())
|
|
686
|
+
userId String
|
|
687
|
+
title String @default("New Chat")
|
|
688
|
+
model String?
|
|
689
|
+
providerId String?
|
|
690
|
+
metadata Json?
|
|
691
|
+
archived Boolean @default(false)
|
|
692
|
+
createdAt DateTime @default(now())
|
|
693
|
+
updatedAt DateTime @updatedAt
|
|
694
|
+
|
|
695
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
696
|
+
provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
697
|
+
messages Message[]
|
|
698
|
+
|
|
699
|
+
@@index([userId])
|
|
700
|
+
@@index([archived])
|
|
701
|
+
@@index([providerId])
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
model Message {
|
|
705
|
+
id String @id @default(cuid())
|
|
706
|
+
conversationId String
|
|
707
|
+
role String
|
|
708
|
+
content String @db.Text
|
|
709
|
+
model String?
|
|
710
|
+
tokens Int?
|
|
711
|
+
createdAt DateTime @default(now())
|
|
712
|
+
|
|
713
|
+
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
714
|
+
|
|
715
|
+
@@index([conversationId])
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
model SystemSetting {
|
|
719
|
+
key String @id
|
|
720
|
+
value String @db.Text
|
|
721
|
+
updatedAt DateTime @updatedAt
|
|
722
|
+
}
|
|
723
|
+
`;
|
|
724
|
+
}
|