@code-rag/api-server 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.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/dashboard/data-collector.d.ts +61 -0
- package/dist/dashboard/data-collector.js +244 -0
- package/dist/dashboard/data-collector.js.map +1 -0
- package/dist/dashboard/data-collector.test.d.ts +1 -0
- package/dist/dashboard/data-collector.test.js +334 -0
- package/dist/dashboard/data-collector.test.js.map +1 -0
- package/dist/dashboard/index.d.ts +10 -0
- package/dist/dashboard/index.js +6 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/routes.d.ts +18 -0
- package/dist/dashboard/routes.js +150 -0
- package/dist/dashboard/routes.js.map +1 -0
- package/dist/dashboard/templates.d.ts +47 -0
- package/dist/dashboard/templates.js +591 -0
- package/dist/dashboard/templates.js.map +1 -0
- package/dist/dashboard/templates.test.d.ts +1 -0
- package/dist/dashboard/templates.test.js +408 -0
- package/dist/dashboard/templates.test.js.map +1 -0
- package/dist/dashboard/types.d.ts +119 -0
- package/dist/dashboard/types.js +4 -0
- package/dist/dashboard/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +37 -0
- package/dist/middleware/auth.js +92 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +26 -0
- package/dist/middleware/rate-limit.js +79 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/openapi.d.ts +14 -0
- package/dist/openapi.js +363 -0
- package/dist/openapi.js.map +1 -0
- package/dist/routes/context.d.ts +15 -0
- package/dist/routes/context.js +84 -0
- package/dist/routes/context.js.map +1 -0
- package/dist/routes/history.d.ts +56 -0
- package/dist/routes/history.js +308 -0
- package/dist/routes/history.js.map +1 -0
- package/dist/routes/index-trigger.d.ts +21 -0
- package/dist/routes/index-trigger.js +47 -0
- package/dist/routes/index-trigger.js.map +1 -0
- package/dist/routes/search.d.ts +24 -0
- package/dist/routes/search.js +85 -0
- package/dist/routes/search.js.map +1 -0
- package/dist/routes/status.d.ts +14 -0
- package/dist/routes/status.js +37 -0
- package/dist/routes/status.js.map +1 -0
- package/dist/routes/team.d.ts +62 -0
- package/dist/routes/team.js +129 -0
- package/dist/routes/team.js.map +1 -0
- package/dist/routes/viewer.d.ts +78 -0
- package/dist/routes/viewer.js +387 -0
- package/dist/routes/viewer.js.map +1 -0
- package/dist/routes/viewer.test.d.ts +1 -0
- package/dist/routes/viewer.test.js +509 -0
- package/dist/routes/viewer.test.js.map +1 -0
- package/dist/server.d.ts +45 -0
- package/dist/server.js +227 -0
- package/dist/server.js.map +1 -0
- package/dist/server.test.d.ts +1 -0
- package/dist/server.test.js +620 -0
- package/dist/server.test.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { TokenBudgetOptimizer, } from '@code-rag/core';
|
|
4
|
+
export const contextRequestSchema = z.object({
|
|
5
|
+
file_path: z.string().min(1, 'file_path must not be empty').refine((s) => !s.includes('..'), 'file_path must not contain path traversal'),
|
|
6
|
+
include_tests: z.boolean().optional().default(true),
|
|
7
|
+
include_interfaces: z.boolean().optional().default(true),
|
|
8
|
+
max_tokens: z.number().int().positive().max(128000).optional().default(8000),
|
|
9
|
+
});
|
|
10
|
+
export function createContextRouter(deps) {
|
|
11
|
+
const router = Router();
|
|
12
|
+
router.post('/', async (req, res) => {
|
|
13
|
+
const parsed = contextRequestSchema.safeParse(req.body);
|
|
14
|
+
if (!parsed.success) {
|
|
15
|
+
res.status(400).json({
|
|
16
|
+
error: 'Validation Error',
|
|
17
|
+
details: parsed.error.issues,
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!deps.hybridSearch || !deps.contextExpander) {
|
|
22
|
+
res.status(503).json({
|
|
23
|
+
error: 'Service Unavailable',
|
|
24
|
+
message: 'Services not initialized. Run indexing first.',
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const { file_path, max_tokens } = parsed.data;
|
|
30
|
+
// Search for chunks matching the file_path
|
|
31
|
+
const searchResult = await deps.hybridSearch.search(file_path, { topK: 20 });
|
|
32
|
+
if (searchResult.isErr()) {
|
|
33
|
+
res.status(500).json({
|
|
34
|
+
error: 'Search Failed',
|
|
35
|
+
message: searchResult.error.message,
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Filter to results that match the file path
|
|
40
|
+
let results = searchResult.value.filter((r) => r.chunk?.filePath?.includes(file_path));
|
|
41
|
+
// Apply optional filters
|
|
42
|
+
if (!parsed.data.include_tests) {
|
|
43
|
+
results = results.filter((r) => !r.chunk?.filePath?.includes('.test.') &&
|
|
44
|
+
!r.chunk?.filePath?.includes('.spec.') &&
|
|
45
|
+
!r.chunk?.filePath?.includes('__tests__'));
|
|
46
|
+
}
|
|
47
|
+
if (!parsed.data.include_interfaces) {
|
|
48
|
+
results = results.filter((r) => r.metadata?.chunkType !== 'interface' &&
|
|
49
|
+
r.metadata?.chunkType !== 'type_alias');
|
|
50
|
+
}
|
|
51
|
+
if (results.length === 0) {
|
|
52
|
+
res.json({
|
|
53
|
+
context: '',
|
|
54
|
+
token_count: 0,
|
|
55
|
+
truncated: false,
|
|
56
|
+
primary_chunks: 0,
|
|
57
|
+
related_chunks: 0,
|
|
58
|
+
message: `No chunks found for file: ${file_path}`,
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Expand context via dependency graph
|
|
63
|
+
const expanded = deps.contextExpander.expand(results);
|
|
64
|
+
// Assemble within token budget
|
|
65
|
+
const optimizer = new TokenBudgetOptimizer({ maxTokens: max_tokens });
|
|
66
|
+
const assembled = optimizer.assemble(expanded);
|
|
67
|
+
res.json({
|
|
68
|
+
context: assembled.content,
|
|
69
|
+
token_count: assembled.tokenCount,
|
|
70
|
+
truncated: assembled.truncated,
|
|
71
|
+
primary_chunks: assembled.primaryChunks.length,
|
|
72
|
+
related_chunks: assembled.relatedChunks.length,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
77
|
+
res.status(500).json({
|
|
78
|
+
error: 'Internal Server Error',
|
|
79
|
+
message,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return router;
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/routes/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const historyEntrySchema: z.ZodObject<{
|
|
4
|
+
query: z.ZodString;
|
|
5
|
+
filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
6
|
+
results_count: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export type HistoryEntryInput = z.infer<typeof historyEntrySchema>;
|
|
9
|
+
export declare const bookmarkSchema: z.ZodObject<{
|
|
10
|
+
name: z.ZodString;
|
|
11
|
+
query: z.ZodString;
|
|
12
|
+
filters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export type BookmarkInput = z.infer<typeof bookmarkSchema>;
|
|
15
|
+
export interface HistoryEntry {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly userId: string;
|
|
18
|
+
readonly query: string;
|
|
19
|
+
readonly filters: Record<string, unknown>;
|
|
20
|
+
readonly resultsCount: number;
|
|
21
|
+
readonly timestamp: Date;
|
|
22
|
+
}
|
|
23
|
+
export interface Bookmark {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly userId: string;
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly query: string;
|
|
28
|
+
readonly filters: Record<string, unknown>;
|
|
29
|
+
readonly createdAt: Date;
|
|
30
|
+
}
|
|
31
|
+
export declare class HistoryStore {
|
|
32
|
+
private readonly history;
|
|
33
|
+
private readonly bookmarks;
|
|
34
|
+
private nextHistoryId;
|
|
35
|
+
private nextBookmarkId;
|
|
36
|
+
private readonly historyFilePath;
|
|
37
|
+
private readonly bookmarkFilePath;
|
|
38
|
+
constructor(historyFilePath?: string, bookmarkFilePath?: string);
|
|
39
|
+
addHistory(userId: string, input: HistoryEntryInput): HistoryEntry;
|
|
40
|
+
getHistory(userId: string, page?: number, pageSize?: number): {
|
|
41
|
+
items: readonly HistoryEntry[];
|
|
42
|
+
total: number;
|
|
43
|
+
page: number;
|
|
44
|
+
pageSize: number;
|
|
45
|
+
};
|
|
46
|
+
deleteHistory(userId: string, id: string): boolean;
|
|
47
|
+
addBookmark(userId: string, input: BookmarkInput): Bookmark;
|
|
48
|
+
getBookmarks(userId: string): readonly Bookmark[];
|
|
49
|
+
deleteBookmark(userId: string, id: string): boolean;
|
|
50
|
+
private persistHistoryEntry;
|
|
51
|
+
private persistBookmarkEntry;
|
|
52
|
+
}
|
|
53
|
+
export interface HistoryRouteDeps {
|
|
54
|
+
readonly historyStore: HistoryStore;
|
|
55
|
+
}
|
|
56
|
+
export declare function createHistoryRouter(deps: HistoryRouteDeps): Router;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { readFileSync, appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Schemas
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export const historyEntrySchema = z.object({
|
|
9
|
+
query: z.string().min(1, 'query must not be empty'),
|
|
10
|
+
filters: z.record(z.string(), z.unknown()).optional(),
|
|
11
|
+
results_count: z.number().int().nonnegative().optional(),
|
|
12
|
+
});
|
|
13
|
+
export const bookmarkSchema = z.object({
|
|
14
|
+
name: z.string().min(1, 'name must not be empty'),
|
|
15
|
+
query: z.string().min(1, 'query must not be empty'),
|
|
16
|
+
filters: z.record(z.string(), z.unknown()).optional(),
|
|
17
|
+
});
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// In-memory store with JSONL persistence
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
export class HistoryStore {
|
|
22
|
+
history = [];
|
|
23
|
+
bookmarks = [];
|
|
24
|
+
nextHistoryId = 1;
|
|
25
|
+
nextBookmarkId = 1;
|
|
26
|
+
historyFilePath;
|
|
27
|
+
bookmarkFilePath;
|
|
28
|
+
constructor(historyFilePath, bookmarkFilePath) {
|
|
29
|
+
this.historyFilePath = historyFilePath;
|
|
30
|
+
this.bookmarkFilePath = bookmarkFilePath;
|
|
31
|
+
// Load existing data
|
|
32
|
+
if (historyFilePath && existsSync(historyFilePath)) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(historyFilePath, 'utf-8');
|
|
35
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
const parsed = JSON.parse(line);
|
|
38
|
+
const entry = {
|
|
39
|
+
...parsed,
|
|
40
|
+
timestamp: new Date(parsed.timestamp),
|
|
41
|
+
};
|
|
42
|
+
this.history.push(entry);
|
|
43
|
+
const idNum = parseInt(parsed.id.replace('hist-', ''), 10);
|
|
44
|
+
if (!isNaN(idNum) && idNum >= this.nextHistoryId) {
|
|
45
|
+
this.nextHistoryId = idNum + 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Corrupt file — start fresh in-memory
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (bookmarkFilePath && existsSync(bookmarkFilePath)) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(bookmarkFilePath, 'utf-8');
|
|
56
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
const parsed = JSON.parse(line);
|
|
59
|
+
const bookmark = {
|
|
60
|
+
...parsed,
|
|
61
|
+
createdAt: new Date(parsed.createdAt),
|
|
62
|
+
};
|
|
63
|
+
this.bookmarks.push(bookmark);
|
|
64
|
+
const idNum = parseInt(parsed.id.replace('bm-', ''), 10);
|
|
65
|
+
if (!isNaN(idNum) && idNum >= this.nextBookmarkId) {
|
|
66
|
+
this.nextBookmarkId = idNum + 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Corrupt file — start fresh
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// -----------------------------------------------------------------------
|
|
76
|
+
// History operations
|
|
77
|
+
// -----------------------------------------------------------------------
|
|
78
|
+
addHistory(userId, input) {
|
|
79
|
+
const entry = {
|
|
80
|
+
id: `hist-${this.nextHistoryId++}`,
|
|
81
|
+
userId,
|
|
82
|
+
query: input.query,
|
|
83
|
+
filters: input.filters ?? {},
|
|
84
|
+
resultsCount: input.results_count ?? 0,
|
|
85
|
+
timestamp: new Date(),
|
|
86
|
+
};
|
|
87
|
+
this.history.push(entry);
|
|
88
|
+
this.persistHistoryEntry(entry);
|
|
89
|
+
return entry;
|
|
90
|
+
}
|
|
91
|
+
getHistory(userId, page = 1, pageSize = 20) {
|
|
92
|
+
const userEntries = this.history
|
|
93
|
+
.filter((e) => e.userId === userId)
|
|
94
|
+
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
95
|
+
const start = (page - 1) * pageSize;
|
|
96
|
+
const items = userEntries.slice(start, start + pageSize);
|
|
97
|
+
return {
|
|
98
|
+
items,
|
|
99
|
+
total: userEntries.length,
|
|
100
|
+
page,
|
|
101
|
+
pageSize,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
deleteHistory(userId, id) {
|
|
105
|
+
const index = this.history.findIndex((e) => e.id === id && e.userId === userId);
|
|
106
|
+
if (index === -1)
|
|
107
|
+
return false;
|
|
108
|
+
this.history.splice(index, 1);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
// -----------------------------------------------------------------------
|
|
112
|
+
// Bookmark operations
|
|
113
|
+
// -----------------------------------------------------------------------
|
|
114
|
+
addBookmark(userId, input) {
|
|
115
|
+
const bookmark = {
|
|
116
|
+
id: `bm-${this.nextBookmarkId++}`,
|
|
117
|
+
userId,
|
|
118
|
+
name: input.name,
|
|
119
|
+
query: input.query,
|
|
120
|
+
filters: input.filters ?? {},
|
|
121
|
+
createdAt: new Date(),
|
|
122
|
+
};
|
|
123
|
+
this.bookmarks.push(bookmark);
|
|
124
|
+
this.persistBookmarkEntry(bookmark);
|
|
125
|
+
return bookmark;
|
|
126
|
+
}
|
|
127
|
+
getBookmarks(userId) {
|
|
128
|
+
return this.bookmarks
|
|
129
|
+
.filter((b) => b.userId === userId)
|
|
130
|
+
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
131
|
+
}
|
|
132
|
+
deleteBookmark(userId, id) {
|
|
133
|
+
const index = this.bookmarks.findIndex((b) => b.id === id && b.userId === userId);
|
|
134
|
+
if (index === -1)
|
|
135
|
+
return false;
|
|
136
|
+
this.bookmarks.splice(index, 1);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
// -----------------------------------------------------------------------
|
|
140
|
+
// Persistence
|
|
141
|
+
// -----------------------------------------------------------------------
|
|
142
|
+
persistHistoryEntry(entry) {
|
|
143
|
+
if (!this.historyFilePath)
|
|
144
|
+
return;
|
|
145
|
+
try {
|
|
146
|
+
const dir = dirname(this.historyFilePath);
|
|
147
|
+
if (!existsSync(dir)) {
|
|
148
|
+
mkdirSync(dir, { recursive: true });
|
|
149
|
+
}
|
|
150
|
+
const line = JSON.stringify({
|
|
151
|
+
...entry,
|
|
152
|
+
timestamp: entry.timestamp.toISOString(),
|
|
153
|
+
});
|
|
154
|
+
appendFileSync(this.historyFilePath, line + '\n');
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Swallow I/O errors
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
persistBookmarkEntry(bookmark) {
|
|
161
|
+
if (!this.bookmarkFilePath)
|
|
162
|
+
return;
|
|
163
|
+
try {
|
|
164
|
+
const dir = dirname(this.bookmarkFilePath);
|
|
165
|
+
if (!existsSync(dir)) {
|
|
166
|
+
mkdirSync(dir, { recursive: true });
|
|
167
|
+
}
|
|
168
|
+
const line = JSON.stringify({
|
|
169
|
+
...bookmark,
|
|
170
|
+
createdAt: bookmark.createdAt.toISOString(),
|
|
171
|
+
});
|
|
172
|
+
appendFileSync(this.bookmarkFilePath, line + '\n');
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Swallow I/O errors
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Helpers
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
function getUserId(req) {
|
|
183
|
+
// Use apiKey identifier, X-User-Id header, or 'anonymous'
|
|
184
|
+
const userHeader = req.headers['x-user-id'];
|
|
185
|
+
if (typeof userHeader === 'string' && userHeader.trim().length > 0) {
|
|
186
|
+
return userHeader.trim();
|
|
187
|
+
}
|
|
188
|
+
if (req.apiKey) {
|
|
189
|
+
return req.apiKey.key.slice(0, 8); // Use first 8 chars of API key as user ID
|
|
190
|
+
}
|
|
191
|
+
return 'anonymous';
|
|
192
|
+
}
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Router
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
export function createHistoryRouter(deps) {
|
|
197
|
+
const router = Router();
|
|
198
|
+
// GET /api/v1/history — list user's query history (paginated)
|
|
199
|
+
router.get('/history', (req, res) => {
|
|
200
|
+
const userId = getUserId(req);
|
|
201
|
+
const page = Math.max(1, parseInt(req.query['page'], 10) || 1);
|
|
202
|
+
const pageSize = Math.min(100, Math.max(1, parseInt(req.query['page_size'], 10) || 20));
|
|
203
|
+
const result = deps.historyStore.getHistory(userId, page, pageSize);
|
|
204
|
+
res.status(200).json({
|
|
205
|
+
items: result.items.map(formatHistoryEntry),
|
|
206
|
+
total: result.total,
|
|
207
|
+
page: result.page,
|
|
208
|
+
page_size: result.pageSize,
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// POST /api/v1/history — record a query in history
|
|
212
|
+
router.post('/history', (req, res) => {
|
|
213
|
+
const parsed = historyEntrySchema.safeParse(req.body);
|
|
214
|
+
if (!parsed.success) {
|
|
215
|
+
res.status(400).json({
|
|
216
|
+
error: 'Validation Error',
|
|
217
|
+
details: parsed.error.issues,
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const userId = getUserId(req);
|
|
222
|
+
const entry = deps.historyStore.addHistory(userId, parsed.data);
|
|
223
|
+
res.status(201).json(formatHistoryEntry(entry));
|
|
224
|
+
});
|
|
225
|
+
// DELETE /api/v1/history/:id — delete history entry
|
|
226
|
+
router.delete('/history/:id', (req, res) => {
|
|
227
|
+
const userId = getUserId(req);
|
|
228
|
+
const id = req.params['id'];
|
|
229
|
+
if (!id) {
|
|
230
|
+
res.status(400).json({ error: 'Missing id parameter' });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const deleted = deps.historyStore.deleteHistory(userId, id);
|
|
234
|
+
if (!deleted) {
|
|
235
|
+
res.status(404).json({
|
|
236
|
+
error: 'Not Found',
|
|
237
|
+
message: `History entry "${id}" not found.`,
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
res.status(204).end();
|
|
242
|
+
});
|
|
243
|
+
// GET /api/v1/bookmarks — list bookmarks
|
|
244
|
+
router.get('/bookmarks', (req, res) => {
|
|
245
|
+
const userId = getUserId(req);
|
|
246
|
+
const bookmarks = deps.historyStore.getBookmarks(userId);
|
|
247
|
+
res.status(200).json({
|
|
248
|
+
items: bookmarks.map(formatBookmark),
|
|
249
|
+
total: bookmarks.length,
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
// POST /api/v1/bookmarks — add bookmark
|
|
253
|
+
router.post('/bookmarks', (req, res) => {
|
|
254
|
+
const parsed = bookmarkSchema.safeParse(req.body);
|
|
255
|
+
if (!parsed.success) {
|
|
256
|
+
res.status(400).json({
|
|
257
|
+
error: 'Validation Error',
|
|
258
|
+
details: parsed.error.issues,
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const userId = getUserId(req);
|
|
263
|
+
const bookmark = deps.historyStore.addBookmark(userId, parsed.data);
|
|
264
|
+
res.status(201).json(formatBookmark(bookmark));
|
|
265
|
+
});
|
|
266
|
+
// DELETE /api/v1/bookmarks/:id — remove bookmark
|
|
267
|
+
router.delete('/bookmarks/:id', (req, res) => {
|
|
268
|
+
const userId = getUserId(req);
|
|
269
|
+
const id = req.params['id'];
|
|
270
|
+
if (!id) {
|
|
271
|
+
res.status(400).json({ error: 'Missing id parameter' });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const deleted = deps.historyStore.deleteBookmark(userId, id);
|
|
275
|
+
if (!deleted) {
|
|
276
|
+
res.status(404).json({
|
|
277
|
+
error: 'Not Found',
|
|
278
|
+
message: `Bookmark "${id}" not found.`,
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
res.status(204).end();
|
|
283
|
+
});
|
|
284
|
+
return router;
|
|
285
|
+
}
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Formatters
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
function formatHistoryEntry(entry) {
|
|
290
|
+
return {
|
|
291
|
+
id: entry.id,
|
|
292
|
+
user_id: entry.userId,
|
|
293
|
+
query: entry.query,
|
|
294
|
+
filters: entry.filters,
|
|
295
|
+
results_count: entry.resultsCount,
|
|
296
|
+
timestamp: entry.timestamp.toISOString(),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function formatBookmark(bookmark) {
|
|
300
|
+
return {
|
|
301
|
+
id: bookmark.id,
|
|
302
|
+
user_id: bookmark.userId,
|
|
303
|
+
name: bookmark.name,
|
|
304
|
+
query: bookmark.query,
|
|
305
|
+
filters: bookmark.filters,
|
|
306
|
+
created_at: bookmark.createdAt.toISOString(),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/routes/history.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const indexTriggerRequestSchema: z.ZodObject<{
|
|
4
|
+
root_dir: z.ZodOptional<z.ZodString>;
|
|
5
|
+
force: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export type IndexTriggerRequest = z.infer<typeof indexTriggerRequestSchema>;
|
|
8
|
+
/**
|
|
9
|
+
* Callback invoked when re-indexing is triggered via the API.
|
|
10
|
+
*/
|
|
11
|
+
export type IndexTriggerCallback = (options: {
|
|
12
|
+
rootDir?: string;
|
|
13
|
+
force: boolean;
|
|
14
|
+
}) => Promise<{
|
|
15
|
+
indexed_files: number;
|
|
16
|
+
duration_ms: number;
|
|
17
|
+
}>;
|
|
18
|
+
export interface IndexTriggerRouteDeps {
|
|
19
|
+
readonly onIndex: IndexTriggerCallback | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function createIndexTriggerRouter(deps: IndexTriggerRouteDeps): Router;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireAdmin } from '../middleware/auth.js';
|
|
4
|
+
export const indexTriggerRequestSchema = z.object({
|
|
5
|
+
root_dir: z.string().min(1, 'root_dir must not be empty').refine((s) => !s.includes('..'), 'root_dir must not contain path traversal').optional(),
|
|
6
|
+
force: z.boolean().optional().default(false),
|
|
7
|
+
});
|
|
8
|
+
export function createIndexTriggerRouter(deps) {
|
|
9
|
+
const router = Router();
|
|
10
|
+
// Admin-only endpoint
|
|
11
|
+
router.post('/', requireAdmin, async (req, res) => {
|
|
12
|
+
const parsed = indexTriggerRequestSchema.safeParse(req.body);
|
|
13
|
+
if (!parsed.success) {
|
|
14
|
+
res.status(400).json({
|
|
15
|
+
error: 'Validation Error',
|
|
16
|
+
details: parsed.error.issues,
|
|
17
|
+
});
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!deps.onIndex) {
|
|
21
|
+
res.status(503).json({
|
|
22
|
+
error: 'Service Unavailable',
|
|
23
|
+
message: 'Indexing service not configured.',
|
|
24
|
+
});
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const result = await deps.onIndex({
|
|
29
|
+
rootDir: parsed.data.root_dir,
|
|
30
|
+
force: parsed.data.force,
|
|
31
|
+
});
|
|
32
|
+
res.json({
|
|
33
|
+
status: 'completed',
|
|
34
|
+
indexed_files: result.indexed_files,
|
|
35
|
+
duration_ms: result.duration_ms,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
40
|
+
res.status(500).json({
|
|
41
|
+
error: 'Indexing Failed',
|
|
42
|
+
message,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return router;
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-trigger.js","sourceRoot":"","sources":["../../src/routes/index-trigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,4BAA4B,CAAC,CAAC,MAAM,CAC9D,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EACxB,0CAA0C,CAC3C,CAAC,QAAQ,EAAE;IACZ,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC7C,CAAC,CAAC;AAgBH,MAAM,UAAU,wBAAwB,CAAC,IAA2B;IAClE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,sBAAsB;IACtB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAChD,MAAM,MAAM,GAAG,yBAAyB,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE7D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,kBAAkB;gBACzB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;aAC7B,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,qBAAqB;gBAC5B,OAAO,EAAE,kCAAkC;aAC5C,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;gBAChC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;gBAC7B,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK;aACzB,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CAAC;gBACP,MAAM,EAAE,WAAW;gBACnB,aAAa,EAAE,MAAM,CAAC,aAAa;gBACnC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACzE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,iBAAiB;gBACxB,OAAO;aACR,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { HybridSearch, ReRanker } from '@code-rag/core';
|
|
4
|
+
export declare const searchRequestSchema: z.ZodObject<{
|
|
5
|
+
query: z.ZodString;
|
|
6
|
+
language: z.ZodOptional<z.ZodString>;
|
|
7
|
+
file_path: z.ZodOptional<z.ZodString>;
|
|
8
|
+
chunk_type: z.ZodOptional<z.ZodString>;
|
|
9
|
+
top_k: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type SearchRequest = z.infer<typeof searchRequestSchema>;
|
|
12
|
+
export interface SearchResponseItem {
|
|
13
|
+
file_path: string;
|
|
14
|
+
chunk_type: string;
|
|
15
|
+
name: string;
|
|
16
|
+
content: string;
|
|
17
|
+
nl_summary: string;
|
|
18
|
+
score: number;
|
|
19
|
+
}
|
|
20
|
+
export interface SearchRouteDeps {
|
|
21
|
+
readonly hybridSearch: HybridSearch | null;
|
|
22
|
+
readonly reranker: ReRanker | null;
|
|
23
|
+
}
|
|
24
|
+
export declare function createSearchRouter(deps: SearchRouteDeps): Router;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export const searchRequestSchema = z.object({
|
|
4
|
+
query: z.string().min(1, 'query must not be empty'),
|
|
5
|
+
language: z.string().optional(),
|
|
6
|
+
file_path: z.string().refine((s) => !s.includes('..'), 'file_path must not contain path traversal').optional(),
|
|
7
|
+
chunk_type: z.string().optional(),
|
|
8
|
+
top_k: z.number().int().positive().max(100).optional().default(10),
|
|
9
|
+
});
|
|
10
|
+
function formatResult(result) {
|
|
11
|
+
return {
|
|
12
|
+
file_path: result.chunk?.filePath ?? '',
|
|
13
|
+
chunk_type: result.metadata?.chunkType ?? 'unknown',
|
|
14
|
+
name: result.metadata?.name ?? '',
|
|
15
|
+
content: result.content,
|
|
16
|
+
nl_summary: result.nlSummary,
|
|
17
|
+
score: result.score,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function createSearchRouter(deps) {
|
|
21
|
+
const router = Router();
|
|
22
|
+
router.post('/', async (req, res) => {
|
|
23
|
+
const parsed = searchRequestSchema.safeParse(req.body);
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
res.status(400).json({
|
|
26
|
+
error: 'Validation Error',
|
|
27
|
+
details: parsed.error.issues,
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!deps.hybridSearch) {
|
|
32
|
+
res.status(503).json({
|
|
33
|
+
error: 'Service Unavailable',
|
|
34
|
+
message: 'Search index not initialized. Run indexing first.',
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const { query, top_k } = parsed.data;
|
|
40
|
+
const searchResult = await deps.hybridSearch.search(query, { topK: top_k });
|
|
41
|
+
if (searchResult.isErr()) {
|
|
42
|
+
res.status(500).json({
|
|
43
|
+
error: 'Search Failed',
|
|
44
|
+
message: searchResult.error.message,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let results = searchResult.value;
|
|
49
|
+
// Apply optional filters
|
|
50
|
+
if (parsed.data.language) {
|
|
51
|
+
const lang = parsed.data.language.toLowerCase();
|
|
52
|
+
results = results.filter((r) => r.chunk?.language?.toLowerCase() === lang);
|
|
53
|
+
}
|
|
54
|
+
if (parsed.data.file_path) {
|
|
55
|
+
const fp = parsed.data.file_path;
|
|
56
|
+
results = results.filter((r) => r.chunk?.filePath?.includes(fp));
|
|
57
|
+
}
|
|
58
|
+
if (parsed.data.chunk_type) {
|
|
59
|
+
const ct = parsed.data.chunk_type;
|
|
60
|
+
results = results.filter((r) => r.metadata?.chunkType === ct);
|
|
61
|
+
}
|
|
62
|
+
// Re-rank if available
|
|
63
|
+
if (deps.reranker) {
|
|
64
|
+
const rerankResult = await deps.reranker.rerank(query, results);
|
|
65
|
+
if (rerankResult.isOk()) {
|
|
66
|
+
results = rerankResult.value;
|
|
67
|
+
}
|
|
68
|
+
// If reranking fails, fall back to original results
|
|
69
|
+
}
|
|
70
|
+
const formatted = results.map(formatResult);
|
|
71
|
+
res.json({
|
|
72
|
+
results: formatted,
|
|
73
|
+
total: formatted.length,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
78
|
+
res.status(500).json({
|
|
79
|
+
error: 'Internal Server Error',
|
|
80
|
+
message,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return router;
|
|
85
|
+
}
|