server/web/src/routes/api/channels/[id]/messages/+server.ts
vegard 592ebdf1d6 Tiptap-editor og mentions→graf-edges
Ny Editor-komponent med Tiptap (bold, italic, code, mentions).
Chat og notater oppretter nå MENTIONS-edges i kunnskapsgrafen
automatisk ved lagring. SpacetimeDB-adapter skriver alltid via
PG API først for edge-atomisitet. RLS SET LOCAL fix i db.ts.

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

106 lines
3.4 KiB
TypeScript

import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { sql } from '$lib/server/db';
export const GET: RequestHandler = async ({ params, url, locals }) => {
if (!locals.workspace || !locals.user) error(401);
const channelId = params.id;
const after = url.searchParams.get('after');
const limit = Math.min(Number(url.searchParams.get('limit') ?? 50), 100);
// Verifiser at kanalen tilhører workspace
const [channel] = await sql`
SELECT c.id FROM channels c
JOIN nodes n ON n.id = c.id
WHERE c.id = ${channelId} AND n.workspace_id = ${locals.workspace.id}
`;
if (!channel) error(404, 'Kanal ikke funnet');
const messages = after
? await sql`
SELECT m.id, m.body, m.message_type, m.created_at, m.reply_to,
u.display_name as author_name, u.authentik_id as author_id
FROM messages m
LEFT JOIN users u ON u.authentik_id = m.author_id
WHERE m.channel_id = ${channelId} AND m.created_at > ${after}
ORDER BY m.created_at ASC
LIMIT ${limit}
`
: await sql`
SELECT m.id, m.body, m.message_type, m.created_at, m.reply_to,
u.display_name as author_name, u.authentik_id as author_id
FROM messages m
LEFT JOIN users u ON u.authentik_id = m.author_id
WHERE m.channel_id = ${channelId}
ORDER BY m.created_at DESC
LIMIT ${limit}
`.then((rows) => rows.reverse());
return json(messages);
};
export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!locals.workspace || !locals.user) error(401);
const workspace = locals.workspace;
const user = locals.user;
const channelId = params.id;
const { body, replyTo, mentions } = await request.json();
if (!body || typeof body !== 'string' || body.trim().length === 0) {
error(400, 'Melding kan ikke være tom');
}
// Verifiser at kanalen tilhører workspace
const [channel] = await sql`
SELECT c.id FROM channels c
JOIN nodes n ON n.id = c.id
WHERE c.id = ${channelId} AND n.workspace_id = ${workspace.id}
`;
if (!channel) error(404, 'Kanal ikke funnet');
// Opprett node + melding i PG
const [message] = await sql`
WITH new_node AS (
INSERT INTO nodes (workspace_id, node_type)
VALUES (${workspace.id}, 'melding')
RETURNING id
)
INSERT INTO messages (id, channel_id, author_id, body, reply_to)
SELECT new_node.id, ${channelId}, ${user.id}, ${body.trim()}, ${replyTo ?? null}
FROM new_node
RETURNING id, body, message_type, created_at, reply_to
`;
// Opprett MENTIONS-edges for hver #-mention
if (Array.isArray(mentions) && mentions.length > 0) {
const entityIds = mentions
.map((m: { id?: string }) => m.id)
.filter((id): id is string => typeof id === 'string' && id !== message.id);
if (entityIds.length > 0) {
// Verifiser at alle nevnte entiteter tilhører workspace
const validEntities = await sql`
SELECT id FROM nodes
WHERE id = ANY(${entityIds}) AND workspace_id = ${workspace.id}
`;
const validIds = new Set(validEntities.map((e) => (e as { id: string }).id));
for (const entityId of entityIds) {
if (!validIds.has(entityId)) continue;
await sql`
INSERT INTO graph_edges (workspace_id, source_id, target_id, relation_type, created_by, origin)
VALUES (${workspace.id}, ${message.id}, ${entityId}, 'MENTIONS', ${user.id}, 'user')
ON CONFLICT (source_id, target_id, relation_type) DO NOTHING
`;
}
}
}
return json({
...message,
author_name: user.name,
author_id: user.id
}, { status: 201 });
};