graph.js 3.8 KB
/**
 * Graph API — 知识图谱 REST 接口
 *
 * GET  /api/graph/nodes?type=Person&limit=100   列出节点
 * GET  /api/graph/nodes/:id                     查询单个节点(含边)
 * POST /api/graph/nodes                         创建/更新节点
 * GET  /api/graph/search?q=关键词               全文搜索
 * POST /api/graph/edges                         创建关系边
 * GET  /api/graph/path?from=id&to=id            查询关系路径
 */

const express = require('express');
const { getGraphStore } = require('../db/GraphStore');

const router = express.Router();

function store() {
    return getGraphStore();
}

// GET /api/graph/nodes?type=Person&limit=100&offset=0&sort=name&order=asc
router.get('/nodes', (req, res) => {
    const { type, limit = 100, offset = 0, sort, order = 'asc' } = req.query;
    const g = store();
    try {
        const lim = Number(limit);
        const off = Number(offset);
        let nodes, total;
        if (type) {
            if (sort) {
                nodes = g.listSorted(type, sort, order, lim, off);
            } else {
                nodes = g.listByType(type, lim, off);
            }
            total = g.countByType(type);
        } else {
            nodes = g.search('', lim, off);
            total = g.countSearch('');
        }
        res.json({ nodes, total, limit: lim, offset: off });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

// GET /api/graph/nodes/:id
router.get('/nodes/:id', (req, res) => {
    const g = store();
    try {
        const node = g.getNode(req.params.id);
        if (!node) return res.status(404).json({ error: 'Node not found' });

        const edges = g.getEdges(req.params.id);
        res.json({ node, edges });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

// POST /api/graph/nodes  { type, ...data }
router.post('/nodes', (req, res) => {
    const { type, ...data } = req.body;
    if (!type) return res.status(400).json({ error: 'type is required' });
    try {
        const node = store().createNode(type, data);
        res.json({ node });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

// GET /api/graph/search?q=xxx&type=Person&limit=50&offset=0
router.get('/search', (req, res) => {
    const { q = '', type, limit = 50, offset = 0 } = req.query;
    if (!q) return res.status(400).json({ error: 'q is required' });
    try {
        const g = store();
        const lim = Number(limit);
        const off = Number(offset);
        let results = g.search(q, lim + (type ? lim : 0), off); // 多取一些再过滤
        if (type) results = results.filter(n => n._type === type);
        const total = g.countSearch(q);
        res.json({ results, total, limit: lim, offset: off });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

// POST /api/graph/edges  { fromId, toId, relation, ...meta }
router.post('/edges', (req, res) => {
    const { fromId, toId, relation, ...meta } = req.body;
    if (!fromId || !toId || !relation) {
        return res.status(400).json({ error: 'fromId, toId, relation are required' });
    }
    try {
        const edge = store().createEdge(fromId, toId, relation, meta);
        res.json({ edge });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

// GET /api/graph/path?from=id&to=id&maxDepth=6
router.get('/path', (req, res) => {
    const { from, to, maxDepth = 6 } = req.query;
    if (!from || !to) return res.status(400).json({ error: 'from and to are required' });
    try {
        const path = store().getRelationshipPath(from, to, Number(maxDepth));
        res.json({ path, found: path !== null, steps: path?.length ?? null });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
});

module.exports = router;