@prih/mcp-graph-memory 1.0.3
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 +15 -0
- package/README.md +512 -0
- package/dist/api/index.js +473 -0
- package/dist/api/rest/code.js +78 -0
- package/dist/api/rest/docs.js +80 -0
- package/dist/api/rest/files.js +64 -0
- package/dist/api/rest/graph.js +56 -0
- package/dist/api/rest/index.js +117 -0
- package/dist/api/rest/knowledge.js +238 -0
- package/dist/api/rest/skills.js +284 -0
- package/dist/api/rest/tasks.js +272 -0
- package/dist/api/rest/tools.js +126 -0
- package/dist/api/rest/validation.js +191 -0
- package/dist/api/rest/websocket.js +65 -0
- package/dist/api/tools/code/get-file-symbols.js +30 -0
- package/dist/api/tools/code/get-symbol.js +22 -0
- package/dist/api/tools/code/list-files.js +18 -0
- package/dist/api/tools/code/search-code.js +27 -0
- package/dist/api/tools/code/search-files.js +22 -0
- package/dist/api/tools/context/get-context.js +19 -0
- package/dist/api/tools/docs/cross-references.js +76 -0
- package/dist/api/tools/docs/explain-symbol.js +55 -0
- package/dist/api/tools/docs/find-examples.js +52 -0
- package/dist/api/tools/docs/get-node.js +24 -0
- package/dist/api/tools/docs/get-toc.js +22 -0
- package/dist/api/tools/docs/list-snippets.js +46 -0
- package/dist/api/tools/docs/list-topics.js +18 -0
- package/dist/api/tools/docs/search-files.js +22 -0
- package/dist/api/tools/docs/search-snippets.js +43 -0
- package/dist/api/tools/docs/search.js +27 -0
- package/dist/api/tools/file-index/get-file-info.js +21 -0
- package/dist/api/tools/file-index/list-all-files.js +28 -0
- package/dist/api/tools/file-index/search-all-files.js +24 -0
- package/dist/api/tools/knowledge/add-attachment.js +31 -0
- package/dist/api/tools/knowledge/create-note.js +20 -0
- package/dist/api/tools/knowledge/create-relation.js +29 -0
- package/dist/api/tools/knowledge/delete-note.js +19 -0
- package/dist/api/tools/knowledge/delete-relation.js +23 -0
- package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
- package/dist/api/tools/knowledge/get-note.js +20 -0
- package/dist/api/tools/knowledge/list-notes.js +18 -0
- package/dist/api/tools/knowledge/list-relations.js +17 -0
- package/dist/api/tools/knowledge/remove-attachment.js +19 -0
- package/dist/api/tools/knowledge/search-notes.js +25 -0
- package/dist/api/tools/knowledge/update-note.js +34 -0
- package/dist/api/tools/skills/add-attachment.js +31 -0
- package/dist/api/tools/skills/bump-usage.js +19 -0
- package/dist/api/tools/skills/create-skill-link.js +25 -0
- package/dist/api/tools/skills/create-skill.js +26 -0
- package/dist/api/tools/skills/delete-skill-link.js +23 -0
- package/dist/api/tools/skills/delete-skill.js +20 -0
- package/dist/api/tools/skills/find-linked-skills.js +25 -0
- package/dist/api/tools/skills/get-skill.js +21 -0
- package/dist/api/tools/skills/link-skill.js +23 -0
- package/dist/api/tools/skills/list-skills.js +20 -0
- package/dist/api/tools/skills/recall-skills.js +18 -0
- package/dist/api/tools/skills/remove-attachment.js +19 -0
- package/dist/api/tools/skills/search-skills.js +25 -0
- package/dist/api/tools/skills/update-skill.js +58 -0
- package/dist/api/tools/tasks/add-attachment.js +31 -0
- package/dist/api/tools/tasks/create-task-link.js +25 -0
- package/dist/api/tools/tasks/create-task.js +25 -0
- package/dist/api/tools/tasks/delete-task-link.js +23 -0
- package/dist/api/tools/tasks/delete-task.js +20 -0
- package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
- package/dist/api/tools/tasks/get-task.js +20 -0
- package/dist/api/tools/tasks/link-task.js +23 -0
- package/dist/api/tools/tasks/list-tasks.js +24 -0
- package/dist/api/tools/tasks/move-task.js +38 -0
- package/dist/api/tools/tasks/remove-attachment.js +19 -0
- package/dist/api/tools/tasks/search-tasks.js +25 -0
- package/dist/api/tools/tasks/update-task.js +55 -0
- package/dist/cli/index.js +451 -0
- package/dist/cli/indexer.js +277 -0
- package/dist/graphs/attachment-types.js +74 -0
- package/dist/graphs/code-types.js +10 -0
- package/dist/graphs/code.js +172 -0
- package/dist/graphs/docs.js +198 -0
- package/dist/graphs/file-index-types.js +10 -0
- package/dist/graphs/file-index.js +310 -0
- package/dist/graphs/file-lang.js +119 -0
- package/dist/graphs/knowledge-types.js +32 -0
- package/dist/graphs/knowledge.js +764 -0
- package/dist/graphs/manager-types.js +87 -0
- package/dist/graphs/skill-types.js +10 -0
- package/dist/graphs/skill.js +1013 -0
- package/dist/graphs/task-types.js +17 -0
- package/dist/graphs/task.js +960 -0
- package/dist/lib/embedder.js +101 -0
- package/dist/lib/events-log.js +400 -0
- package/dist/lib/file-import.js +327 -0
- package/dist/lib/file-mirror.js +446 -0
- package/dist/lib/frontmatter.js +17 -0
- package/dist/lib/mirror-watcher.js +637 -0
- package/dist/lib/multi-config.js +254 -0
- package/dist/lib/parsers/code.js +246 -0
- package/dist/lib/parsers/codeblock.js +66 -0
- package/dist/lib/parsers/docs.js +196 -0
- package/dist/lib/project-manager.js +418 -0
- package/dist/lib/promise-queue.js +22 -0
- package/dist/lib/search/bm25.js +167 -0
- package/dist/lib/search/code.js +103 -0
- package/dist/lib/search/docs.js +108 -0
- package/dist/lib/search/file-index.js +31 -0
- package/dist/lib/search/files.js +61 -0
- package/dist/lib/search/knowledge.js +101 -0
- package/dist/lib/search/skills.js +104 -0
- package/dist/lib/search/tasks.js +103 -0
- package/dist/lib/watcher.js +67 -0
- package/package.json +83 -0
- package/ui/README.md +54 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createFilesRouter = createFilesRouter;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const express_1 = require("express");
|
|
9
|
+
const validation_1 = require("../../api/rest/validation");
|
|
10
|
+
function createFilesRouter() {
|
|
11
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
12
|
+
function getProject(req) {
|
|
13
|
+
return req.project;
|
|
14
|
+
}
|
|
15
|
+
// List all files
|
|
16
|
+
router.get('/', (0, validation_1.validateQuery)(validation_1.fileListSchema), (req, res, next) => {
|
|
17
|
+
try {
|
|
18
|
+
const p = getProject(req);
|
|
19
|
+
const q = req.validatedQuery;
|
|
20
|
+
const files = p.fileIndexManager.listAllFiles(q);
|
|
21
|
+
res.json({ results: files });
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
next(err);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
// Search files
|
|
28
|
+
router.get('/search', (0, validation_1.validateQuery)(validation_1.searchQuerySchema), async (req, res, next) => {
|
|
29
|
+
try {
|
|
30
|
+
const p = getProject(req);
|
|
31
|
+
const q = req.validatedQuery;
|
|
32
|
+
const results = await p.fileIndexManager.search(q.q, {
|
|
33
|
+
topK: q.topK,
|
|
34
|
+
minScore: q.minScore,
|
|
35
|
+
});
|
|
36
|
+
res.json({ results });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
next(err);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// Get file info
|
|
43
|
+
router.get('/info', (req, res, next) => {
|
|
44
|
+
try {
|
|
45
|
+
const p = getProject(req);
|
|
46
|
+
const filePath = req.query.path;
|
|
47
|
+
if (!filePath)
|
|
48
|
+
return res.status(400).json({ error: 'path query parameter required' });
|
|
49
|
+
// Prevent path traversal
|
|
50
|
+
const normalized = path_1.default.normalize(filePath);
|
|
51
|
+
if (normalized.startsWith('..') || path_1.default.isAbsolute(normalized)) {
|
|
52
|
+
return res.status(400).json({ error: 'Invalid path' });
|
|
53
|
+
}
|
|
54
|
+
const info = p.fileIndexManager.getFileInfo(normalized);
|
|
55
|
+
if (!info)
|
|
56
|
+
return res.status(404).json({ error: 'File not found' });
|
|
57
|
+
res.json(info);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
next(err);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return router;
|
|
64
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGraphRouter = createGraphRouter;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const validation_1 = require("../../api/rest/validation");
|
|
6
|
+
function exportGraph(graph, graphName) {
|
|
7
|
+
const nodes = [];
|
|
8
|
+
const edges = [];
|
|
9
|
+
graph.forEachNode((id, attrs) => {
|
|
10
|
+
// Skip proxy nodes and embeddings for transfer size
|
|
11
|
+
const { embedding, fileEmbedding, ...rest } = attrs;
|
|
12
|
+
nodes.push({ id, graph: graphName, ...rest });
|
|
13
|
+
});
|
|
14
|
+
graph.forEachEdge((_edge, attrs, source, target) => {
|
|
15
|
+
edges.push({ source, target, graph: graphName, ...attrs });
|
|
16
|
+
});
|
|
17
|
+
return { nodes, edges };
|
|
18
|
+
}
|
|
19
|
+
function createGraphRouter() {
|
|
20
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
21
|
+
function getProject(req) {
|
|
22
|
+
return req.project;
|
|
23
|
+
}
|
|
24
|
+
router.get('/', (0, validation_1.validateQuery)(validation_1.graphExportSchema), (req, res, next) => {
|
|
25
|
+
try {
|
|
26
|
+
const p = getProject(req);
|
|
27
|
+
const scope = req.validatedQuery.scope;
|
|
28
|
+
const allNodes = [];
|
|
29
|
+
const allEdges = [];
|
|
30
|
+
const add = (g, name) => {
|
|
31
|
+
if (!g)
|
|
32
|
+
return;
|
|
33
|
+
const exp = exportGraph(g, name);
|
|
34
|
+
allNodes.push(...exp.nodes);
|
|
35
|
+
allEdges.push(...exp.edges);
|
|
36
|
+
};
|
|
37
|
+
if (scope === 'all' || scope === 'docs')
|
|
38
|
+
add(p.docGraph, 'docs');
|
|
39
|
+
if (scope === 'all' || scope === 'code')
|
|
40
|
+
add(p.codeGraph, 'code');
|
|
41
|
+
if (scope === 'all' || scope === 'knowledge')
|
|
42
|
+
add(p.knowledgeGraph, 'knowledge');
|
|
43
|
+
if (scope === 'all' || scope === 'tasks')
|
|
44
|
+
add(p.taskGraph, 'tasks');
|
|
45
|
+
if (scope === 'all' || scope === 'files')
|
|
46
|
+
add(p.fileIndexGraph, 'files');
|
|
47
|
+
if (scope === 'all' || scope === 'skills')
|
|
48
|
+
add(p.skillGraph, 'skills');
|
|
49
|
+
res.json({ nodes: allNodes, edges: allEdges });
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
next(err);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return router;
|
|
56
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createRestApp = createRestApp;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const express_1 = __importDefault(require("express"));
|
|
9
|
+
const cors_1 = __importDefault(require("cors"));
|
|
10
|
+
const knowledge_1 = require("../../api/rest/knowledge");
|
|
11
|
+
const tasks_1 = require("../../api/rest/tasks");
|
|
12
|
+
const skills_1 = require("../../api/rest/skills");
|
|
13
|
+
const docs_1 = require("../../api/rest/docs");
|
|
14
|
+
const code_1 = require("../../api/rest/code");
|
|
15
|
+
const files_1 = require("../../api/rest/files");
|
|
16
|
+
const graph_1 = require("../../api/rest/graph");
|
|
17
|
+
const tools_1 = require("../../api/rest/tools");
|
|
18
|
+
/**
|
|
19
|
+
* Create an Express app with all REST routes mounted.
|
|
20
|
+
* Each route uses the ProjectManager to look up project-specific graphs.
|
|
21
|
+
*/
|
|
22
|
+
function createRestApp(projectManager) {
|
|
23
|
+
const app = (0, express_1.default)();
|
|
24
|
+
app.use((0, cors_1.default)());
|
|
25
|
+
app.use(express_1.default.json({ limit: '10mb' }));
|
|
26
|
+
// Security headers
|
|
27
|
+
app.use((_req, res, next) => {
|
|
28
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
29
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
30
|
+
next();
|
|
31
|
+
});
|
|
32
|
+
// Project resolution middleware — injects project instance into req
|
|
33
|
+
app.param('projectId', (req, _res, next, projectId) => {
|
|
34
|
+
const project = projectManager.getProject(projectId);
|
|
35
|
+
if (!project) {
|
|
36
|
+
return _res.status(404).json({ error: `Project "${projectId}" not found` });
|
|
37
|
+
}
|
|
38
|
+
req.project = project;
|
|
39
|
+
next();
|
|
40
|
+
});
|
|
41
|
+
// List projects
|
|
42
|
+
app.get('/api/projects', (_req, res) => {
|
|
43
|
+
const projects = projectManager.listProjects().map(id => {
|
|
44
|
+
const p = projectManager.getProject(id);
|
|
45
|
+
return {
|
|
46
|
+
id,
|
|
47
|
+
projectDir: p.config.projectDir,
|
|
48
|
+
workspaceId: p.workspaceId ?? null,
|
|
49
|
+
stats: {
|
|
50
|
+
docs: p.docGraph ? p.docGraph.order : 0,
|
|
51
|
+
code: p.codeGraph ? p.codeGraph.order : 0,
|
|
52
|
+
knowledge: p.knowledgeGraph.order,
|
|
53
|
+
files: p.fileIndexGraph.order,
|
|
54
|
+
tasks: p.taskGraph.order,
|
|
55
|
+
skills: p.skillGraph.order,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
res.json({ results: projects });
|
|
60
|
+
});
|
|
61
|
+
// List workspaces
|
|
62
|
+
app.get('/api/workspaces', (_req, res) => {
|
|
63
|
+
const workspaces = projectManager.listWorkspaces().map(id => {
|
|
64
|
+
const ws = projectManager.getWorkspace(id);
|
|
65
|
+
return {
|
|
66
|
+
id,
|
|
67
|
+
projects: ws.config.projects,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
res.json({ results: workspaces });
|
|
71
|
+
});
|
|
72
|
+
// Project stats
|
|
73
|
+
app.get('/api/projects/:projectId/stats', (req, res) => {
|
|
74
|
+
const p = req.project;
|
|
75
|
+
res.json({
|
|
76
|
+
docs: p.docGraph ? { nodes: p.docGraph.order, edges: p.docGraph.size } : null,
|
|
77
|
+
code: p.codeGraph ? { nodes: p.codeGraph.order, edges: p.codeGraph.size } : null,
|
|
78
|
+
knowledge: { nodes: p.knowledgeGraph.order, edges: p.knowledgeGraph.size },
|
|
79
|
+
fileIndex: { nodes: p.fileIndexGraph.order, edges: p.fileIndexGraph.size },
|
|
80
|
+
tasks: { nodes: p.taskGraph.order, edges: p.taskGraph.size },
|
|
81
|
+
skills: { nodes: p.skillGraph.order, edges: p.skillGraph.size },
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// Mount domain routers
|
|
85
|
+
app.use('/api/projects/:projectId/knowledge', (0, knowledge_1.createKnowledgeRouter)());
|
|
86
|
+
app.use('/api/projects/:projectId/tasks', (0, tasks_1.createTasksRouter)());
|
|
87
|
+
app.use('/api/projects/:projectId/skills', (0, skills_1.createSkillsRouter)());
|
|
88
|
+
app.use('/api/projects/:projectId/docs', (0, docs_1.createDocsRouter)());
|
|
89
|
+
app.use('/api/projects/:projectId/code', (0, code_1.createCodeRouter)());
|
|
90
|
+
app.use('/api/projects/:projectId/files', (0, files_1.createFilesRouter)());
|
|
91
|
+
app.use('/api/projects/:projectId/graph', (0, graph_1.createGraphRouter)());
|
|
92
|
+
app.use('/api/projects/:projectId/tools', (0, tools_1.createToolsRouter)(projectManager));
|
|
93
|
+
// Serve UI static files (ui/dist)
|
|
94
|
+
const uiDist = path_1.default.resolve(__dirname, '../../../ui/dist');
|
|
95
|
+
app.use(express_1.default.static(uiDist));
|
|
96
|
+
// SPA fallback: serve index.html for non-API routes
|
|
97
|
+
app.get('/{*splat}', (_req, res, next) => {
|
|
98
|
+
if (_req.path.startsWith('/api/'))
|
|
99
|
+
return next();
|
|
100
|
+
res.sendFile(path_1.default.join(uiDist, 'index.html'), (err) => {
|
|
101
|
+
if (err)
|
|
102
|
+
next();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
// Error handler
|
|
106
|
+
app.use((err, _req, res, _next) => {
|
|
107
|
+
if (err.name === 'ZodError') {
|
|
108
|
+
return res.status(400).json({ error: 'Validation error', details: err.issues });
|
|
109
|
+
}
|
|
110
|
+
if (err.type === 'entity.parse.failed' || (err instanceof SyntaxError && 'body' in err)) {
|
|
111
|
+
return res.status(400).json({ error: 'Invalid JSON' });
|
|
112
|
+
}
|
|
113
|
+
process.stderr.write(`[rest] Error: ${err.stack || err}\n`);
|
|
114
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
115
|
+
});
|
|
116
|
+
return app;
|
|
117
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createKnowledgeRouter = createKnowledgeRouter;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const mime_1 = __importDefault(require("mime"));
|
|
9
|
+
const express_1 = require("express");
|
|
10
|
+
const multer_1 = __importDefault(require("multer"));
|
|
11
|
+
const validation_1 = require("../../api/rest/validation");
|
|
12
|
+
const manager_types_1 = require("../../graphs/manager-types");
|
|
13
|
+
const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } });
|
|
14
|
+
function createKnowledgeRouter() {
|
|
15
|
+
const router = (0, express_1.Router)({ mergeParams: true });
|
|
16
|
+
function getProject(req) {
|
|
17
|
+
return req.project;
|
|
18
|
+
}
|
|
19
|
+
// List notes
|
|
20
|
+
router.get('/notes', (0, validation_1.validateQuery)(validation_1.noteListSchema), (req, res, next) => {
|
|
21
|
+
try {
|
|
22
|
+
const p = getProject(req);
|
|
23
|
+
const q = req.validatedQuery;
|
|
24
|
+
const notes = p.knowledgeManager.listNotes(q.filter, q.tag, q.limit);
|
|
25
|
+
res.json({ results: notes });
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
next(err);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Search notes
|
|
32
|
+
router.get('/search', (0, validation_1.validateQuery)(validation_1.noteSearchSchema), async (req, res, next) => {
|
|
33
|
+
try {
|
|
34
|
+
const p = getProject(req);
|
|
35
|
+
const q = req.validatedQuery;
|
|
36
|
+
const results = await p.knowledgeManager.searchNotes(q.q, {
|
|
37
|
+
topK: q.topK,
|
|
38
|
+
minScore: q.minScore,
|
|
39
|
+
searchMode: q.searchMode,
|
|
40
|
+
});
|
|
41
|
+
res.json({ results });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
next(err);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// Get note
|
|
48
|
+
router.get('/notes/:noteId', (req, res, next) => {
|
|
49
|
+
try {
|
|
50
|
+
const p = getProject(req);
|
|
51
|
+
const note = p.knowledgeManager.getNote(req.params.noteId);
|
|
52
|
+
if (!note)
|
|
53
|
+
return res.status(404).json({ error: 'Note not found' });
|
|
54
|
+
res.json(note);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
next(err);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
// Create note
|
|
61
|
+
router.post('/notes', (0, validation_1.validateBody)(validation_1.createNoteSchema), async (req, res, next) => {
|
|
62
|
+
try {
|
|
63
|
+
const p = getProject(req);
|
|
64
|
+
const { title, content, tags } = req.body;
|
|
65
|
+
const created = await p.mutationQueue.enqueue(async () => {
|
|
66
|
+
const noteId = await p.knowledgeManager.createNote(title, content, tags);
|
|
67
|
+
return p.knowledgeManager.getNote(noteId);
|
|
68
|
+
});
|
|
69
|
+
res.status(201).json(created);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
next(err);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// Update note
|
|
76
|
+
router.put('/notes/:noteId', (0, validation_1.validateBody)(validation_1.updateNoteSchema), async (req, res, next) => {
|
|
77
|
+
try {
|
|
78
|
+
const p = getProject(req);
|
|
79
|
+
const noteId = req.params.noteId;
|
|
80
|
+
const { version, ...patch } = req.body;
|
|
81
|
+
const result = await p.mutationQueue.enqueue(async () => {
|
|
82
|
+
const ok = await p.knowledgeManager.updateNote(noteId, patch, version);
|
|
83
|
+
if (!ok)
|
|
84
|
+
return null;
|
|
85
|
+
return p.knowledgeManager.getNote(noteId);
|
|
86
|
+
});
|
|
87
|
+
if (!result)
|
|
88
|
+
return res.status(404).json({ error: 'Note not found' });
|
|
89
|
+
res.json(result);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof manager_types_1.VersionConflictError) {
|
|
93
|
+
return res.status(409).json({ error: 'version_conflict', current: err.current, expected: err.expected });
|
|
94
|
+
}
|
|
95
|
+
next(err);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// Delete note
|
|
99
|
+
router.delete('/notes/:noteId', async (req, res, next) => {
|
|
100
|
+
try {
|
|
101
|
+
const p = getProject(req);
|
|
102
|
+
const noteId = req.params.noteId;
|
|
103
|
+
const ok = await p.mutationQueue.enqueue(async () => {
|
|
104
|
+
return p.knowledgeManager.deleteNote(noteId);
|
|
105
|
+
});
|
|
106
|
+
if (!ok)
|
|
107
|
+
return res.status(404).json({ error: 'Note not found' });
|
|
108
|
+
res.status(204).end();
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
next(err);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// Create relation
|
|
115
|
+
router.post('/relations', (0, validation_1.validateBody)(validation_1.createRelationSchema), async (req, res, next) => {
|
|
116
|
+
try {
|
|
117
|
+
const p = getProject(req);
|
|
118
|
+
const { fromId, toId, kind, targetGraph, projectId } = req.body;
|
|
119
|
+
const ok = await p.mutationQueue.enqueue(async () => {
|
|
120
|
+
return p.knowledgeManager.createRelation(fromId, toId, kind, targetGraph, projectId);
|
|
121
|
+
});
|
|
122
|
+
if (!ok)
|
|
123
|
+
return res.status(400).json({ error: 'Failed to create relation' });
|
|
124
|
+
res.status(201).json({ fromId, toId, kind, targetGraph: targetGraph || undefined });
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
next(err);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
// Delete relation
|
|
131
|
+
router.delete('/relations', (0, validation_1.validateBody)(validation_1.createRelationSchema.pick({ fromId: true, toId: true, targetGraph: true, projectId: true })), async (req, res, next) => {
|
|
132
|
+
try {
|
|
133
|
+
const p = getProject(req);
|
|
134
|
+
const { fromId, toId, targetGraph, projectId } = req.body;
|
|
135
|
+
const ok = await p.mutationQueue.enqueue(async () => {
|
|
136
|
+
return p.knowledgeManager.deleteRelation(fromId, toId, targetGraph, projectId);
|
|
137
|
+
});
|
|
138
|
+
if (!ok)
|
|
139
|
+
return res.status(404).json({ error: 'Relation not found' });
|
|
140
|
+
res.status(204).end();
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
next(err);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// List relations for a note
|
|
147
|
+
router.get('/notes/:noteId/relations', (req, res, next) => {
|
|
148
|
+
try {
|
|
149
|
+
const p = getProject(req);
|
|
150
|
+
const relations = p.knowledgeManager.listRelations(req.params.noteId);
|
|
151
|
+
res.json({ results: relations });
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
next(err);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// Find notes linked to an external entity
|
|
158
|
+
router.get('/linked', (0, validation_1.validateQuery)(validation_1.linkedQuerySchema), (req, res, next) => {
|
|
159
|
+
try {
|
|
160
|
+
const p = getProject(req);
|
|
161
|
+
const { targetGraph, targetNodeId, kind, projectId } = req.validatedQuery;
|
|
162
|
+
const notes = p.knowledgeManager.findLinkedNotes(targetGraph, targetNodeId, kind, projectId ?? req.params.projectId);
|
|
163
|
+
res.json({ results: notes });
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
next(err);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// -- Attachments --
|
|
170
|
+
// Upload attachment
|
|
171
|
+
router.post('/notes/:noteId/attachments', upload.single('file'), async (req, res, next) => {
|
|
172
|
+
try {
|
|
173
|
+
const p = getProject(req);
|
|
174
|
+
const noteId = req.params.noteId;
|
|
175
|
+
const file = req.file;
|
|
176
|
+
if (!file)
|
|
177
|
+
return res.status(400).json({ error: 'No file uploaded' });
|
|
178
|
+
const meta = await p.mutationQueue.enqueue(async () => {
|
|
179
|
+
return p.knowledgeManager.addAttachment(noteId, file.originalname, file.buffer);
|
|
180
|
+
});
|
|
181
|
+
if (!meta)
|
|
182
|
+
return res.status(404).json({ error: 'Note not found' });
|
|
183
|
+
res.status(201).json(meta);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
next(err);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// List attachments
|
|
190
|
+
router.get('/notes/:noteId/attachments', (req, res, next) => {
|
|
191
|
+
try {
|
|
192
|
+
const p = getProject(req);
|
|
193
|
+
const attachments = p.knowledgeManager.listAttachments(req.params.noteId);
|
|
194
|
+
res.json({ results: attachments });
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
next(err);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// Download attachment
|
|
201
|
+
router.get('/notes/:noteId/attachments/:filename', (req, res, next) => {
|
|
202
|
+
try {
|
|
203
|
+
const p = getProject(req);
|
|
204
|
+
const filename = validation_1.attachmentFilenameSchema.parse(req.params.filename);
|
|
205
|
+
const filePath = p.knowledgeManager.getAttachmentPath(req.params.noteId, filename);
|
|
206
|
+
if (!filePath)
|
|
207
|
+
return res.status(404).json({ error: 'Attachment not found' });
|
|
208
|
+
const mimeType = mime_1.default.getType(filePath) ?? 'application/octet-stream';
|
|
209
|
+
res.setHeader('Content-Type', mimeType);
|
|
210
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
211
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
212
|
+
const stream = fs_1.default.createReadStream(filePath);
|
|
213
|
+
stream.on('error', (err) => next(err));
|
|
214
|
+
stream.pipe(res);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
next(err);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// Delete attachment
|
|
221
|
+
router.delete('/notes/:noteId/attachments/:filename', async (req, res, next) => {
|
|
222
|
+
try {
|
|
223
|
+
const p = getProject(req);
|
|
224
|
+
const noteId = req.params.noteId;
|
|
225
|
+
const filename = validation_1.attachmentFilenameSchema.parse(req.params.filename);
|
|
226
|
+
const ok = await p.mutationQueue.enqueue(async () => {
|
|
227
|
+
return p.knowledgeManager.removeAttachment(noteId, filename);
|
|
228
|
+
});
|
|
229
|
+
if (!ok)
|
|
230
|
+
return res.status(404).json({ error: 'Attachment not found' });
|
|
231
|
+
res.status(204).end();
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
next(err);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
return router;
|
|
238
|
+
}
|