server/web/src/routes/api/entities/[entityId]/+server.ts
vegard 1d47119b1e Kunnskapsgraf CRUD API: entities, edges, søk og traversering
Syv nye API-endepunkter for kunnskapsgrafen:

Entities:
- GET/POST /api/entities — list, søk (name+aliases), filtrer på type
- GET/PATCH/DELETE /api/entities/:id — hent (m/edge_count), oppdater, slett
- GET /api/entities/:id/edges — relasjoner med retningsfilter

Graf:
- POST /api/graph/edges — opprett relasjon (upsert)
- DELETE /api/graph/edges/:id — slett relasjon
- GET /api/graph/search — fulltekstsøk (entiteter + transkripsjoner FTS)
- GET /api/graph/traverse/:nodeId — recursive CTE, D3.js/Vis.js-format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 15:37:00 +01:00

73 lines
2.6 KiB
TypeScript

import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { sql } from '$lib/server/db';
/** GET /api/entities/:entityId — Hent entitet med edge-count */
export const GET: RequestHandler = async ({ params, locals }) => {
if (!locals.workspace || !locals.user) error(401);
const [entity] = await sql`
SELECT e.id, e.name, e.type, e.aliases, e.avatar_url,
n.created_at, n.updated_at,
(SELECT COUNT(*) FROM graph_edges ge
WHERE ge.source_id = e.id OR ge.target_id = e.id) AS edge_count
FROM entities e
JOIN nodes n ON n.id = e.id
WHERE e.id = ${params.entityId} AND n.workspace_id = ${locals.workspace.id}
`;
if (!entity) error(404, 'Entitet ikke funnet');
return json(entity);
};
/** PATCH /api/entities/:entityId — Oppdater entitet */
export const PATCH: RequestHandler = async ({ params, request, locals }) => {
if (!locals.workspace || !locals.user) error(401);
const updates = await request.json();
const [existing] = await sql`
SELECT e.id FROM entities e
JOIN nodes n ON n.id = e.id
WHERE e.id = ${params.entityId} AND n.workspace_id = ${locals.workspace.id}
`;
if (!existing) error(404, 'Entitet ikke funnet');
if (updates.type) {
const validTypes = ['person', 'organisasjon', 'sted', 'tema', 'konsept'];
if (!validTypes.includes(updates.type)) {
error(400, `type må være en av: ${validTypes.join(', ')}`);
}
}
const [updated] = await sql`
UPDATE entities SET
name = COALESCE(${updates.name?.trim() ?? null}, name),
type = COALESCE(${updates.type ?? null}, type),
aliases = CASE WHEN ${updates.aliases !== undefined} THEN ${updates.aliases ?? []} ELSE aliases END,
avatar_url = CASE WHEN ${updates.avatar_url !== undefined} THEN ${updates.avatar_url ?? null} ELSE avatar_url END
WHERE id = ${params.entityId}
RETURNING id, name, type, aliases, avatar_url,
(SELECT created_at FROM nodes WHERE id = entities.id) AS created_at,
(SELECT updated_at FROM nodes WHERE id = entities.id) AS updated_at
`;
return json(updated);
};
/** DELETE /api/entities/:entityId — Slett entitet */
export const DELETE: RequestHandler = async ({ params, locals }) => {
if (!locals.workspace || !locals.user) error(401);
const [entity] = await sql`
SELECT e.id FROM entities e
JOIN nodes n ON n.id = e.id
WHERE e.id = ${params.entityId} AND n.workspace_id = ${locals.workspace.id}
`;
if (!entity) error(404, 'Entitet ikke funnet');
// Slett node (cascader til entities + graph_edges)
await sql`DELETE FROM nodes WHERE id = ${params.entityId}`;
return new Response(null, { status: 204 });
};