@layer-ai/core 0.1.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 (49) hide show
  1. package/dist/index.d.ts +13 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +15 -0
  4. package/dist/lib/db/postgres.d.ts +27 -0
  5. package/dist/lib/db/postgres.d.ts.map +1 -0
  6. package/dist/lib/db/postgres.js +168 -0
  7. package/dist/lib/db/redis.d.ts +12 -0
  8. package/dist/lib/db/redis.d.ts.map +1 -0
  9. package/dist/lib/db/redis.js +95 -0
  10. package/dist/middleware/auth.d.ts +22 -0
  11. package/dist/middleware/auth.d.ts.map +1 -0
  12. package/dist/middleware/auth.js +89 -0
  13. package/dist/routes/auth.d.ts +4 -0
  14. package/dist/routes/auth.d.ts.map +1 -0
  15. package/dist/routes/auth.js +82 -0
  16. package/dist/routes/complete.d.ts +4 -0
  17. package/dist/routes/complete.d.ts.map +1 -0
  18. package/dist/routes/complete.js +223 -0
  19. package/dist/routes/gates.d.ts +4 -0
  20. package/dist/routes/gates.d.ts.map +1 -0
  21. package/dist/routes/gates.js +262 -0
  22. package/dist/routes/keys.d.ts +4 -0
  23. package/dist/routes/keys.d.ts.map +1 -0
  24. package/dist/routes/keys.js +70 -0
  25. package/dist/routes/logs.d.ts +4 -0
  26. package/dist/routes/logs.d.ts.map +1 -0
  27. package/dist/routes/logs.js +120 -0
  28. package/dist/services/providers/anthropic.d.ts +18 -0
  29. package/dist/services/providers/anthropic.d.ts.map +1 -0
  30. package/dist/services/providers/anthropic.js +55 -0
  31. package/dist/services/providers/base-adapter.d.ts +32 -0
  32. package/dist/services/providers/base-adapter.d.ts.map +1 -0
  33. package/dist/services/providers/base-adapter.js +89 -0
  34. package/dist/services/providers/google.d.ts +18 -0
  35. package/dist/services/providers/google.d.ts.map +1 -0
  36. package/dist/services/providers/google.js +39 -0
  37. package/dist/services/providers/openai-adapter.d.ts +19 -0
  38. package/dist/services/providers/openai-adapter.d.ts.map +1 -0
  39. package/dist/services/providers/openai-adapter.js +240 -0
  40. package/dist/services/providers/openai.d.ts +17 -0
  41. package/dist/services/providers/openai.d.ts.map +1 -0
  42. package/dist/services/providers/openai.js +43 -0
  43. package/dist/services/providers/test-openai-adapter.d.ts +2 -0
  44. package/dist/services/providers/test-openai-adapter.d.ts.map +1 -0
  45. package/dist/services/providers/test-openai-adapter.js +118 -0
  46. package/dist/services/task-analysis.d.ts +7 -0
  47. package/dist/services/task-analysis.d.ts.map +1 -0
  48. package/dist/services/task-analysis.js +74 -0
  49. package/package.json +53 -0
@@ -0,0 +1,223 @@
1
+ import { Router } from 'express';
2
+ import { db } from '../lib/db/postgres.js';
3
+ import { cache } from '../lib/db/redis.js';
4
+ import { authenticate } from '../middleware/auth.js';
5
+ import { OpenAIAdapter } from '../services/providers/openai-adapter.js';
6
+ import * as anthropic from '../services/providers/anthropic.js';
7
+ import * as google from '../services/providers/google.js';
8
+ import { MODEL_REGISTRY, OverrideField } from '@layer-ai/sdk';
9
+ const router = Router();
10
+ // MARK:- Helper Functions
11
+ function isOverrideAllowed(allowOverrides, field) {
12
+ if (allowOverrides === undefined || allowOverrides === null || allowOverrides === true)
13
+ return true;
14
+ if (allowOverrides === false)
15
+ return false;
16
+ return allowOverrides[field] ?? false;
17
+ }
18
+ async function getGateConfig(userId, gateName) {
19
+ let gateConfig = await cache.getGate(userId, gateName);
20
+ if (!gateConfig) {
21
+ gateConfig = await db.getGateByUserAndName(userId, gateName);
22
+ if (gateConfig) {
23
+ await cache.setGate(userId, gateName, gateConfig);
24
+ }
25
+ }
26
+ return gateConfig;
27
+ }
28
+ function resolveFinalParams(gateConfig, requestParams) {
29
+ const { model, temperature, maxTokens, topP, messages } = requestParams;
30
+ let finalModel = gateConfig.model;
31
+ if (model && isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Model) && MODEL_REGISTRY[model]) {
32
+ finalModel = model;
33
+ }
34
+ let finalTemperature = gateConfig.temperature;
35
+ if (isOverrideAllowed(gateConfig.allowOverrides, OverrideField.Temperature)) {
36
+ finalTemperature = temperature ?? gateConfig.temperature;
37
+ }
38
+ let finalMaxTokens = gateConfig.maxTokens;
39
+ if (isOverrideAllowed(gateConfig.allowOverrides, OverrideField.MaxTokens)) {
40
+ finalMaxTokens = maxTokens ?? gateConfig.maxTokens;
41
+ }
42
+ let finalTopP = gateConfig.topP;
43
+ if (isOverrideAllowed(gateConfig.allowOverrides, OverrideField.TopP)) {
44
+ finalTopP = topP ?? gateConfig.topP;
45
+ }
46
+ return {
47
+ model: finalModel,
48
+ messages,
49
+ temperature: finalTemperature,
50
+ maxTokens: finalMaxTokens,
51
+ topP: finalTopP,
52
+ systemPrompt: gateConfig.systemPrompt,
53
+ };
54
+ }
55
+ /**
56
+ * MIGRATION IN PROGRESS: Moving to normalized adapter pattern.
57
+ * OpenAI now uses the new adapter. Other providers will follow.
58
+ * This temporary conversion layer will be removed after all providers are migrated.
59
+ */
60
+ async function callProvider(params) {
61
+ const provider = MODEL_REGISTRY[params.model].provider;
62
+ switch (provider) {
63
+ case 'openai': {
64
+ const adapter = new OpenAIAdapter();
65
+ const layerResponse = await adapter.call({
66
+ gate: 'internal',
67
+ model: params.model,
68
+ type: 'chat',
69
+ data: {
70
+ messages: params.messages,
71
+ systemPrompt: params.systemPrompt,
72
+ temperature: params.temperature,
73
+ maxTokens: params.maxTokens,
74
+ topP: params.topP,
75
+ },
76
+ });
77
+ return {
78
+ content: layerResponse.content || '',
79
+ promptTokens: layerResponse.usage?.promptTokens || 0,
80
+ completionTokens: layerResponse.usage?.completionTokens || 0,
81
+ totalTokens: layerResponse.usage?.totalTokens || 0,
82
+ costUsd: layerResponse.cost || 0,
83
+ };
84
+ }
85
+ case 'anthropic':
86
+ return await anthropic.createCompletion(params);
87
+ case 'google':
88
+ return await google.createCompletion(params);
89
+ default:
90
+ throw new Error(`Unknown provider: ${provider}`);
91
+ }
92
+ }
93
+ function getModelsToTry(gateConfig, primaryModel) {
94
+ const modelsToTry = [primaryModel];
95
+ if (gateConfig.routingStrategy === 'fallback' && gateConfig.fallbackModels?.length) {
96
+ modelsToTry.push(...gateConfig.fallbackModels);
97
+ }
98
+ return modelsToTry;
99
+ }
100
+ async function executeWithFallback(params, modelsToTry) {
101
+ let result = null;
102
+ let lastError = null;
103
+ let modelUsed = params.model;
104
+ for (const modelToTry of modelsToTry) {
105
+ try {
106
+ const modelParams = { ...params, model: modelToTry };
107
+ result = await callProvider(modelParams);
108
+ modelUsed = modelToTry;
109
+ break;
110
+ }
111
+ catch (error) {
112
+ lastError = error;
113
+ console.log(`Model ${modelToTry} failed, trying next fallback...`, error instanceof Error ? error.message : error);
114
+ continue;
115
+ }
116
+ }
117
+ if (!result) {
118
+ throw lastError || new Error('All models failed');
119
+ }
120
+ return { result, modelUsed };
121
+ }
122
+ async function executeWithRoundRobin(gateConfig, params) {
123
+ if (!gateConfig.fallbackModels?.length) {
124
+ const result = await callProvider(params);
125
+ return { result, modelUsed: params.model };
126
+ }
127
+ const allModels = [gateConfig.model, ...gateConfig.fallbackModels];
128
+ const modelIndex = Math.floor(Math.random() * allModels.length);
129
+ const selectedModel = allModels[modelIndex];
130
+ const modelParams = { ...params, model: selectedModel };
131
+ const result = await callProvider(modelParams);
132
+ return { result, modelUsed: selectedModel };
133
+ }
134
+ async function executeWithRouting(gateConfig, params) {
135
+ const modelsToTry = getModelsToTry(gateConfig, params.model);
136
+ switch (gateConfig.routingStrategy) {
137
+ case 'fallback':
138
+ return await executeWithFallback(params, modelsToTry);
139
+ case 'round-robin':
140
+ return await executeWithRoundRobin(gateConfig, params);
141
+ case 'single':
142
+ default:
143
+ const result = await callProvider(params);
144
+ return { result, modelUsed: params.model };
145
+ }
146
+ }
147
+ // MARK:- Route Handler
148
+ router.post('/', authenticate, async (req, res) => {
149
+ const startTime = Date.now();
150
+ if (!req.userId) {
151
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
152
+ return;
153
+ }
154
+ const userId = req.userId;
155
+ try {
156
+ const { gate: gateName, messages, model, temperature, maxTokens, topP } = req.body;
157
+ if (!gateName) {
158
+ res.status(400).json({ error: 'bad_request', message: 'Missing required field: gate' });
159
+ return;
160
+ }
161
+ if (!messages || !Array.isArray(messages) || messages.length === 0) {
162
+ res.status(400).json({ error: 'bad_request', message: 'Missing required field: messages' });
163
+ return;
164
+ }
165
+ const gateConfig = await getGateConfig(userId, gateName);
166
+ if (!gateConfig) {
167
+ res.status(404).json({ error: 'not_found', message: `Gate "${gateName}" not found` });
168
+ return;
169
+ }
170
+ const finalParams = resolveFinalParams(gateConfig, { model, temperature, maxTokens, topP, messages });
171
+ const { result, modelUsed } = await executeWithRouting(gateConfig, finalParams);
172
+ const latencyMs = Date.now() - startTime;
173
+ db.logRequest({
174
+ userId,
175
+ gateId: gateConfig.id,
176
+ gateName,
177
+ modelRequested: model || gateConfig.model,
178
+ modelUsed: modelUsed,
179
+ promptTokens: result.promptTokens,
180
+ completionTokens: result.completionTokens,
181
+ totalTokens: result.totalTokens,
182
+ costUsd: result.costUsd,
183
+ latencyMs,
184
+ success: true,
185
+ errorMessage: null,
186
+ userAgent: req.headers['user-agent'] || null,
187
+ ipAddress: req.ip || null,
188
+ }).catch(err => console.error('Failed to log request:', err));
189
+ const response = {
190
+ content: result?.content,
191
+ model: modelUsed,
192
+ usage: {
193
+ promptTokens: result.promptTokens,
194
+ completionTokens: result.completionTokens,
195
+ totalTokens: result.totalTokens,
196
+ },
197
+ };
198
+ res.json(response);
199
+ }
200
+ catch (error) {
201
+ const latencyMs = Date.now() - startTime;
202
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
203
+ db.logRequest({
204
+ userId,
205
+ gateId: null,
206
+ gateName: req.body?.gate || null,
207
+ modelRequested: null,
208
+ modelUsed: null,
209
+ promptTokens: 0,
210
+ completionTokens: 0,
211
+ totalTokens: 0,
212
+ costUsd: 0,
213
+ latencyMs,
214
+ success: false,
215
+ errorMessage,
216
+ userAgent: req.headers['user-agent'] || null,
217
+ ipAddress: req.ip || null,
218
+ }).catch(err => console.error('Failed to log request:', err));
219
+ console.error('Completion error:', error);
220
+ res.status(500).json({ error: 'internal_error', message: errorMessage });
221
+ }
222
+ });
223
+ export default router;
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=gates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/routes/gates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAOpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA+SpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,262 @@
1
+ import { Router } from 'express';
2
+ import { db } from '../lib/db/postgres.js';
3
+ import { cache } from '../lib/db/redis.js';
4
+ import { authenticate } from '../middleware/auth.js';
5
+ import { MODEL_REGISTRY } from '@layer-ai/sdk';
6
+ const router = Router();
7
+ // All routes require authentication (SDK auth with Bearer token)
8
+ router.use(authenticate);
9
+ // POST / - Create a new gate
10
+ router.post('/', async (req, res) => {
11
+ if (!req.userId) {
12
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
13
+ return;
14
+ }
15
+ try {
16
+ const { name, description, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels } = req.body;
17
+ if (!name || !model) {
18
+ res.status(400).json({ error: 'bad_request', message: 'Missing required fields: name and model' });
19
+ return;
20
+ }
21
+ if (!MODEL_REGISTRY[model]) {
22
+ res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
23
+ return;
24
+ }
25
+ const existing = await db.getGateByUserAndName(req.userId, name);
26
+ if (existing) {
27
+ res.status(409).json({ error: 'conflict', message: `Gate "${name}" already exists` });
28
+ return;
29
+ }
30
+ const gate = await db.createGate(req.userId, {
31
+ name,
32
+ description,
33
+ model,
34
+ systemPrompt,
35
+ allowOverrides,
36
+ temperature,
37
+ maxTokens,
38
+ topP,
39
+ tags,
40
+ routingStrategy,
41
+ fallbackModels,
42
+ });
43
+ res.status(201).json(gate);
44
+ }
45
+ catch (error) {
46
+ console.error('Create gate error:', error);
47
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create gate' });
48
+ }
49
+ });
50
+ // GET / - List all the gates for user
51
+ router.get('/', async (req, res) => {
52
+ if (!req.userId) {
53
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
54
+ return;
55
+ }
56
+ try {
57
+ const gates = await db.getGatesForUser(req.userId);
58
+ res.json(gates);
59
+ }
60
+ catch (error) {
61
+ console.error('List gates error:', error);
62
+ res.status(500).json({ error: 'internal_error', message: 'Failed to list gates' });
63
+ }
64
+ });
65
+ // GET /name/:name - Get a single gate by name
66
+ router.get('/name/:name', async (req, res) => {
67
+ if (!req.userId) {
68
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
69
+ return;
70
+ }
71
+ try {
72
+ const gate = await db.getGateByUserAndName(req.userId, req.params.name);
73
+ if (!gate) {
74
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
75
+ return;
76
+ }
77
+ res.json(gate);
78
+ }
79
+ catch (error) {
80
+ console.error('Get gate by name error:', error);
81
+ res.status(500).json({ error: 'internal_error', message: 'Failed to get gate' });
82
+ }
83
+ });
84
+ // GET /:id - Get a single gate by ID
85
+ router.get('/:id', async (req, res) => {
86
+ if (!req.userId) {
87
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
88
+ return;
89
+ }
90
+ try {
91
+ const gate = await db.getGateById(req.params.id);
92
+ if (!gate) {
93
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
94
+ return;
95
+ }
96
+ if (gate.userId !== req.userId) {
97
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
98
+ return;
99
+ }
100
+ res.json(gate);
101
+ }
102
+ catch (error) {
103
+ console.error('Get gate error:', error);
104
+ res.status(500).json({ error: 'internal_error', message: 'Failed to get gate' });
105
+ }
106
+ });
107
+ // PATCH /name/:name - Update a gate by name
108
+ router.patch('/name/:name', async (req, res) => {
109
+ if (!req.userId) {
110
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
111
+ return;
112
+ }
113
+ try {
114
+ const { description, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels } = req.body;
115
+ const existing = await db.getGateByUserAndName(req.userId, req.params.name);
116
+ if (!existing) {
117
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
118
+ return;
119
+ }
120
+ if (model && !MODEL_REGISTRY[model]) {
121
+ res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
122
+ return;
123
+ }
124
+ const updated = await db.updateGate(existing.id, {
125
+ description,
126
+ model,
127
+ systemPrompt,
128
+ allowOverrides,
129
+ temperature,
130
+ maxTokens,
131
+ topP,
132
+ tags,
133
+ routingStrategy,
134
+ fallbackModels,
135
+ });
136
+ await cache.invalidateGate(req.userId, existing.name);
137
+ res.json(updated);
138
+ }
139
+ catch (error) {
140
+ console.error('Update gate by name error:', error);
141
+ res.status(500).json({ error: 'internal_error', message: 'Failed to update gate' });
142
+ }
143
+ });
144
+ // PATCH /:id - Update a gate by ID
145
+ router.patch('/:id', async (req, res) => {
146
+ if (!req.userId) {
147
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
148
+ return;
149
+ }
150
+ try {
151
+ const { description, model, systemPrompt, allowOverrides, temperature, maxTokens, topP, tags, routingStrategy, fallbackModels } = req.body;
152
+ const existing = await db.getGateById(req.params.id);
153
+ if (!existing) {
154
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
155
+ return;
156
+ }
157
+ if (existing.userId !== req.userId) {
158
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
159
+ return;
160
+ }
161
+ if (model && !MODEL_REGISTRY[model]) {
162
+ res.status(400).json({ error: 'bad_request', message: `Unsupported model: ${model}` });
163
+ return;
164
+ }
165
+ const updated = await db.updateGate(req.params.id, {
166
+ description,
167
+ model,
168
+ systemPrompt,
169
+ allowOverrides,
170
+ temperature,
171
+ maxTokens,
172
+ topP,
173
+ tags,
174
+ routingStrategy,
175
+ fallbackModels,
176
+ });
177
+ await cache.invalidateGate(req.userId, existing.name);
178
+ res.json(updated);
179
+ }
180
+ catch (error) {
181
+ console.error('Update gate error:', error);
182
+ res.status(500).json({ error: 'internal_error', message: 'Failed to update gate' });
183
+ }
184
+ });
185
+ // DELETE /name/:name - Delete a gate by name
186
+ router.delete('/name/:name', async (req, res) => {
187
+ if (!req.userId) {
188
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
189
+ return;
190
+ }
191
+ try {
192
+ const existing = await db.getGateByUserAndName(req.userId, req.params.name);
193
+ if (!existing) {
194
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
195
+ return;
196
+ }
197
+ await db.deleteGate(existing.id);
198
+ await cache.invalidateGate(req.userId, existing.name);
199
+ res.status(204).send();
200
+ }
201
+ catch (error) {
202
+ console.error('Delete gate by name error:', error);
203
+ res.status(500).json({ error: 'internal_error', message: 'Failed to delete gate' });
204
+ }
205
+ });
206
+ // DELETE /:id - Delete a gate by ID
207
+ router.delete('/:id', async (req, res) => {
208
+ if (!req.userId) {
209
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
210
+ return;
211
+ }
212
+ try {
213
+ const existing = await db.getGateById(req.params.id);
214
+ if (!existing) {
215
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
216
+ return;
217
+ }
218
+ if (existing.userId !== req.userId) {
219
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
220
+ return;
221
+ }
222
+ await db.deleteGate(req.params.id);
223
+ await cache.invalidateGate(req.userId, existing.name);
224
+ res.status(204).send();
225
+ }
226
+ catch (error) {
227
+ console.error('Delete gate error:', error);
228
+ res.status(500).json({ error: 'internal_error', message: 'Failed to delete gate' });
229
+ }
230
+ });
231
+ // GET /:name/suggestions - Get AI-powered model suggestions for a gate
232
+ router.get('/:name/suggestions', async (req, res) => {
233
+ if (!req.userId) {
234
+ res.status(401).json({ error: 'unauthorized', message: 'Missing user ID' });
235
+ return;
236
+ }
237
+ try {
238
+ const { name } = req.params;
239
+ const gate = await db.getGateByUserAndName(req.userId, name);
240
+ if (!gate) {
241
+ res.status(404).json({ error: 'not_found', message: 'Gate not found' });
242
+ return;
243
+ }
244
+ if (!gate.description) {
245
+ res.status(400).json({ error: 'bad_request', message: 'Gate must have a description for AI recommendations' });
246
+ return;
247
+ }
248
+ const userPreferences = {
249
+ costWeight: gate.costWeight,
250
+ latencyWeight: gate.latencyWeight,
251
+ qualityWeight: gate.qualityWeight
252
+ };
253
+ const { analyzeTask } = await import('../services/task-analysis.js');
254
+ const suggestions = await analyzeTask(gate.description, userPreferences);
255
+ res.json(suggestions);
256
+ }
257
+ catch (error) {
258
+ console.error('Get suggestions error:', error);
259
+ res.status(500).json({ error: 'internal_error', message: 'Failed to fetch suggestions' });
260
+ }
261
+ });
262
+ export default router;
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/routes/keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAKpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA4EpC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { Router } from 'express';
2
+ import crypto from 'crypto';
3
+ import { db } from '../lib/db/postgres.js';
4
+ import { authenticate } from '../middleware/auth.js';
5
+ const router = Router();
6
+ // All routes require sdk authentication
7
+ router.use(authenticate);
8
+ // Generate a random API key
9
+ function generateApiKey() {
10
+ const randomBytes = crypto.randomBytes(32).toString('hex');
11
+ return `layer_${randomBytes}`;
12
+ }
13
+ // Hash an API key for storage
14
+ function hashApiKey(key) {
15
+ return crypto.createHash('sha256').update(key).digest('hex');
16
+ }
17
+ // GET /api/keys - List user's API keys
18
+ router.get('/', async (req, res) => {
19
+ try {
20
+ const keys = await db.getApiKeysForUser(req.userId);
21
+ res.json(keys);
22
+ }
23
+ catch (error) {
24
+ console.error('Get keys error:', error);
25
+ res.status(500).json({ error: 'internal_error', message: 'Failed to get API keys' });
26
+ }
27
+ });
28
+ // POST /api/keys - Generate new API key
29
+ router.post('/', async (req, res) => {
30
+ try {
31
+ const { name } = req.body;
32
+ if (!name) {
33
+ res.status(400).json({ error: 'bad_request', message: 'Key name required' });
34
+ return;
35
+ }
36
+ const key = generateApiKey();
37
+ const keyHash = hashApiKey(key);
38
+ const keyPrefix = key.substring(0, 12);
39
+ const apiKey = await db.createApiKey(req.userId, keyHash, keyPrefix, name);
40
+ // Return full key only once
41
+ res.status(201).json({
42
+ id: apiKey.id,
43
+ name: apiKey.name,
44
+ key: key,
45
+ keyPrefix: apiKey.keyPrefix,
46
+ createdAt: apiKey.createdAt,
47
+ });
48
+ }
49
+ catch (error) {
50
+ console.error('Create key error:', error);
51
+ res.status(500).json({ error: 'internal_error', message: 'Failed to create API key' });
52
+ }
53
+ });
54
+ // DELETE /api/keys/:id - Delete an API key
55
+ router.delete('/:id', async (req, res) => {
56
+ try {
57
+ const { id } = req.params;
58
+ const deleted = await db.deleteApiKey(id, req.userId);
59
+ if (!deleted) {
60
+ res.status(404).json({ error: 'not_found', message: 'API key not found' });
61
+ return;
62
+ }
63
+ res.status(204).send();
64
+ }
65
+ catch (error) {
66
+ console.error('Delete key error:', error);
67
+ res.status(500).json({ error: 'internal_error', message: 'Failed to delete API key' });
68
+ }
69
+ });
70
+ export default router;
@@ -0,0 +1,4 @@
1
+ import type { Router as RouterType } from 'express';
2
+ declare const router: RouterType;
3
+ export default router;
4
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/routes/logs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAIpD,QAAA,MAAM,MAAM,EAAE,UAAqB,CAAC;AA2IpC,eAAe,MAAM,CAAC"}