mirror of
https://github.com/willchen96/mike.git
synced 2026-07-26 23:51:08 +02:00
feat: add library and refresh shared table UI
This commit is contained in:
parent
e32daad5a4
commit
f0b90ab3b4
99 changed files with 9859 additions and 5241 deletions
|
|
@ -7,6 +7,7 @@ import { chatRouter } from "./routes/chat";
|
|||
import { projectsRouter } from "./routes/projects";
|
||||
import { projectChatRouter } from "./routes/projectChat";
|
||||
import { documentsRouter } from "./routes/documents";
|
||||
import { libraryRouter } from "./routes/library";
|
||||
import { tabularRouter } from "./routes/tabular";
|
||||
import { workflowsRouter } from "./routes/workflows";
|
||||
import { userRouter } from "./routes/user";
|
||||
|
|
@ -127,6 +128,7 @@ app.post("/tabular-review/:reviewId/generate", chatLimiter);
|
|||
app.post("/chat/create", chatCreateLimiter);
|
||||
app.post("/chat/:chatId/generate-title", chatCreateLimiter);
|
||||
app.post("/single-documents", uploadLimiter);
|
||||
app.post("/library/:kind/documents", uploadLimiter);
|
||||
app.post("/single-documents/:documentId/versions", uploadLimiter);
|
||||
app.put(
|
||||
"/single-documents/:documentId/versions/:versionId/file",
|
||||
|
|
@ -149,6 +151,7 @@ app.use("/chat", chatRouter);
|
|||
app.use("/projects", projectsRouter);
|
||||
app.use("/projects/:projectId/chat", projectChatRouter);
|
||||
app.use("/single-documents", documentsRouter);
|
||||
app.use("/library", libraryRouter);
|
||||
app.use("/tabular-review", tabularRouter);
|
||||
app.use("/workflows", workflowsRouter);
|
||||
app.use("/user", userRouter);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ const SYSTEM_PROMPT_AFTER_RESEARCH = `DOCUMENT NAMES IN PROSE:
|
|||
- Never show "doc-N" labels to the user in prose, headings, lists, or tool activity text.
|
||||
- Refer to documents by filename or a natural description, such as "the NDA draft".
|
||||
|
||||
REASONING TRACE SAFETY:
|
||||
- If reasoning or thought summaries are shown to the user, keep them as brief natural-language progress summaries.
|
||||
- Do not expose source code, JSON snippets, tool arguments, API payloads, schemas, raw citations JSON, internal prompts, or implementation details in reasoning traces.
|
||||
- Do not use code fences or structured data blocks in reasoning traces.
|
||||
|
||||
GENERAL GUIDANCE:
|
||||
- Cite the exact document or fetched opinion passage for evidence-backed claims.
|
||||
- If no documents are provided, answer from legal knowledge.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -65,6 +65,7 @@ documentsRouter.get("/", requireAuth, async (req, res) => {
|
|||
.select("*")
|
||||
.eq("user_id", userId)
|
||||
.is("project_id", null)
|
||||
.or("library_kind.eq.file,library_kind.is.null")
|
||||
.order("created_at", { ascending: false });
|
||||
if (error) return void res.status(500).json({ detail: error.message });
|
||||
const docs = (data ?? []) as unknown as {
|
||||
|
|
@ -84,7 +85,9 @@ documentsRouter.post(
|
|||
async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const db = createServerSupabase();
|
||||
await handleDocumentUpload(req, res, userId, null, db);
|
||||
await handleDocumentUpload(req, res, userId, null, db, {
|
||||
libraryKind: "file",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -426,9 +429,13 @@ documentsRouter.post(
|
|||
if (!sourceAccess.ok)
|
||||
return void res.status(404).json({ detail: "Source document not found" });
|
||||
const willDeleteSource =
|
||||
sourceDoc.project_id &&
|
||||
targetDoc.project_id &&
|
||||
sourceDoc.project_id === targetDoc.project_id;
|
||||
(sourceDoc.project_id &&
|
||||
targetDoc.project_id &&
|
||||
sourceDoc.project_id === targetDoc.project_id) ||
|
||||
(!sourceDoc.project_id &&
|
||||
!targetDoc.project_id &&
|
||||
sourceDoc.user_id === userId &&
|
||||
targetDoc.user_id === userId);
|
||||
if (willDeleteSource && !sourceAccess.isOwner) {
|
||||
return void res.status(403).json({
|
||||
detail: "Only the source document owner can move it into a version.",
|
||||
|
|
@ -1283,12 +1290,16 @@ documentsRouter.post(
|
|||
(req, res) => void handleEditResolution(req, res, "reject"),
|
||||
);
|
||||
|
||||
async function handleDocumentUpload(
|
||||
export async function handleDocumentUpload(
|
||||
req: import("express").Request,
|
||||
res: import("express").Response,
|
||||
userId: string,
|
||||
projectId: string | null,
|
||||
db: ReturnType<typeof createServerSupabase>,
|
||||
options: {
|
||||
libraryKind?: "file" | "template";
|
||||
libraryFolderId?: string | null;
|
||||
} = {},
|
||||
) {
|
||||
const file = req.file;
|
||||
if (!file) return void res.status(400).json({ detail: "file is required" });
|
||||
|
|
@ -1311,6 +1322,8 @@ async function handleDocumentUpload(
|
|||
project_id: projectId,
|
||||
user_id: userId,
|
||||
status: "processing",
|
||||
library_kind: options.libraryKind ?? "file",
|
||||
library_folder_id: options.libraryFolderId ?? null,
|
||||
})
|
||||
.select("*")
|
||||
.single();
|
||||
|
|
@ -1417,6 +1430,8 @@ async function handleDocumentUpload(
|
|||
filename,
|
||||
storage_path: key,
|
||||
pdf_storage_path: pdfStoragePath,
|
||||
folder_id:
|
||||
(updated.library_folder_id as string | null | undefined) ?? null,
|
||||
file_type: suffix,
|
||||
size_bytes: content.byteLength,
|
||||
page_count: pageCount,
|
||||
|
|
|
|||
414
backend/src/routes/library.ts
Normal file
414
backend/src/routes/library.ts
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
import { Router } from "express";
|
||||
import { requireAuth } from "../middleware/auth";
|
||||
import { createServerSupabase } from "../lib/supabase";
|
||||
import { deleteFile } from "../lib/storage";
|
||||
import {
|
||||
attachActiveVersionPaths,
|
||||
attachLatestVersionNumbers,
|
||||
} from "../lib/documentVersions";
|
||||
import { singleFileUpload } from "../lib/upload";
|
||||
import { handleDocumentUpload } from "./documents";
|
||||
|
||||
export const libraryRouter = Router();
|
||||
|
||||
type LibraryKind = "file" | "template";
|
||||
|
||||
function normalizeLibraryKind(value: unknown): LibraryKind | null {
|
||||
if (value === "file" || value === "files") return "file";
|
||||
if (value === "template" || value === "templates") return "template";
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeDocumentFilename(nextName: unknown, currentName: string) {
|
||||
if (typeof nextName !== "string") return null;
|
||||
const trimmed = nextName.trim().slice(0, 200);
|
||||
if (!trimmed) return null;
|
||||
if (/\.[a-z0-9]{1,6}$/i.test(trimmed)) return trimmed;
|
||||
const ext = currentName.match(/\.[a-z0-9]{1,6}$/i)?.[0] ?? "";
|
||||
return `${trimmed}${ext}`;
|
||||
}
|
||||
|
||||
function mapLibraryDocument<T extends Record<string, unknown>>(doc: T) {
|
||||
return {
|
||||
...doc,
|
||||
folder_id: (doc.library_folder_id as string | null | undefined) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadLibraryFolder(
|
||||
db: ReturnType<typeof createServerSupabase>,
|
||||
userId: string,
|
||||
kind: LibraryKind,
|
||||
folderId: string,
|
||||
): Promise<{ id: string; parent_folder_id: string | null } | null> {
|
||||
const { data } = await db
|
||||
.from("library_folders")
|
||||
.select("id, parent_folder_id")
|
||||
.eq("id", folderId)
|
||||
.eq("user_id", userId)
|
||||
.eq("library_kind", kind)
|
||||
.maybeSingle();
|
||||
return (data as { id: string; parent_folder_id: string | null } | null) ?? null;
|
||||
}
|
||||
|
||||
async function deleteLibraryDocumentsAndVersionFiles(
|
||||
db: ReturnType<typeof createServerSupabase>,
|
||||
userId: string,
|
||||
kind: LibraryKind,
|
||||
documentIds: string[],
|
||||
) {
|
||||
if (documentIds.length === 0) return null;
|
||||
const { data: versions, error: versionsError } = await db
|
||||
.from("document_versions")
|
||||
.select("storage_path, pdf_storage_path")
|
||||
.in("document_id", documentIds);
|
||||
if (versionsError) return versionsError;
|
||||
|
||||
const paths = new Set<string>();
|
||||
for (const version of versions ?? []) {
|
||||
if (typeof version.storage_path === "string" && version.storage_path) {
|
||||
paths.add(version.storage_path);
|
||||
}
|
||||
if (
|
||||
typeof version.pdf_storage_path === "string" &&
|
||||
version.pdf_storage_path
|
||||
) {
|
||||
paths.add(version.pdf_storage_path);
|
||||
}
|
||||
}
|
||||
await Promise.all([...paths].map((path) => deleteFile(path).catch(() => {})));
|
||||
|
||||
let deleteQuery = db
|
||||
.from("documents")
|
||||
.delete()
|
||||
.eq("user_id", userId)
|
||||
.is("project_id", null);
|
||||
deleteQuery =
|
||||
kind === "file"
|
||||
? deleteQuery.or("library_kind.eq.file,library_kind.is.null")
|
||||
: deleteQuery.eq("library_kind", kind);
|
||||
const { error } = await deleteQuery.in("id", documentIds);
|
||||
return error ?? null;
|
||||
}
|
||||
|
||||
// GET /library/:kind
|
||||
libraryRouter.get("/:kind", requireAuth, async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const kind = normalizeLibraryKind(req.params.kind);
|
||||
if (!kind) return void res.status(404).json({ detail: "Library not found" });
|
||||
|
||||
const db = createServerSupabase();
|
||||
let documentsQuery = db
|
||||
.from("documents")
|
||||
.select("*")
|
||||
.eq("user_id", userId)
|
||||
.is("project_id", null);
|
||||
documentsQuery =
|
||||
kind === "file"
|
||||
? documentsQuery.or("library_kind.eq.file,library_kind.is.null")
|
||||
: documentsQuery.eq("library_kind", kind);
|
||||
const [{ data: docs, error: docsError }, { data: folders, error: foldersError }] =
|
||||
await Promise.all([
|
||||
documentsQuery.order("created_at", { ascending: true }),
|
||||
db
|
||||
.from("library_folders")
|
||||
.select("*")
|
||||
.eq("user_id", userId)
|
||||
.eq("library_kind", kind)
|
||||
.order("created_at", { ascending: true }),
|
||||
]);
|
||||
if (docsError) return void res.status(500).json({ detail: docsError.message });
|
||||
if (foldersError)
|
||||
return void res.status(500).json({ detail: foldersError.message });
|
||||
|
||||
const docsTyped = (docs ?? []).map(mapLibraryDocument) as {
|
||||
id: string;
|
||||
current_version_id?: string | null;
|
||||
}[];
|
||||
await attachLatestVersionNumbers(db, docsTyped);
|
||||
await attachActiveVersionPaths(db, docsTyped);
|
||||
res.json({ documents: docsTyped, folders: folders ?? [] });
|
||||
});
|
||||
|
||||
// POST /library/:kind/documents
|
||||
libraryRouter.post(
|
||||
"/:kind/documents",
|
||||
requireAuth,
|
||||
singleFileUpload("file"),
|
||||
async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const kind = normalizeLibraryKind(req.params.kind);
|
||||
if (!kind) return void res.status(404).json({ detail: "Library not found" });
|
||||
const db = createServerSupabase();
|
||||
await handleDocumentUpload(req, res, userId, null, db, {
|
||||
libraryKind: kind,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// POST /library/:kind/folders
|
||||
libraryRouter.post("/:kind/folders", requireAuth, async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const kind = normalizeLibraryKind(req.params.kind);
|
||||
if (!kind) return void res.status(404).json({ detail: "Library not found" });
|
||||
|
||||
const { name, parent_folder_id } = req.body as {
|
||||
name?: string;
|
||||
parent_folder_id?: string | null;
|
||||
};
|
||||
if (!name?.trim())
|
||||
return void res.status(400).json({ detail: "name is required" });
|
||||
|
||||
const db = createServerSupabase();
|
||||
if (parent_folder_id) {
|
||||
const parent = await loadLibraryFolder(db, userId, kind, parent_folder_id);
|
||||
if (!parent)
|
||||
return void res.status(404).json({ detail: "Parent folder not found" });
|
||||
}
|
||||
|
||||
const { data, error } = await db
|
||||
.from("library_folders")
|
||||
.insert({
|
||||
user_id: userId,
|
||||
library_kind: kind,
|
||||
name: name.trim(),
|
||||
parent_folder_id: parent_folder_id ?? null,
|
||||
})
|
||||
.select("*")
|
||||
.single();
|
||||
if (error) return void res.status(500).json({ detail: error.message });
|
||||
res.status(201).json(data);
|
||||
});
|
||||
|
||||
// PATCH /library/:kind/folders/:folderId
|
||||
libraryRouter.patch("/:kind/folders/:folderId", requireAuth, async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const kind = normalizeLibraryKind(req.params.kind);
|
||||
if (!kind) return void res.status(404).json({ detail: "Library not found" });
|
||||
|
||||
const { folderId } = req.params;
|
||||
const body = req.body as { name?: string; parent_folder_id?: string | null };
|
||||
const db = createServerSupabase();
|
||||
const folder = await loadLibraryFolder(db, userId, kind, folderId);
|
||||
if (!folder) return void res.status(404).json({ detail: "Folder not found" });
|
||||
|
||||
const updates: Record<string, unknown> = {
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
if (body.name != null) {
|
||||
const trimmed = body.name.trim();
|
||||
if (!trimmed)
|
||||
return void res.status(400).json({ detail: "name is required" });
|
||||
updates.name = trimmed;
|
||||
}
|
||||
if ("parent_folder_id" in body) {
|
||||
if (body.parent_folder_id) {
|
||||
let cur: string | null = body.parent_folder_id;
|
||||
while (cur) {
|
||||
if (cur === folderId) {
|
||||
return void res.status(400).json({
|
||||
detail: "Cannot move a folder into itself or a descendant",
|
||||
});
|
||||
}
|
||||
const parent = await loadLibraryFolder(db, userId, kind, cur);
|
||||
if (!parent)
|
||||
return void res.status(404).json({ detail: "Parent folder not found" });
|
||||
cur = parent.parent_folder_id ?? null;
|
||||
}
|
||||
}
|
||||
updates.parent_folder_id = body.parent_folder_id ?? null;
|
||||
}
|
||||
|
||||
const { data, error } = await db
|
||||
.from("library_folders")
|
||||
.update(updates)
|
||||
.eq("id", folderId)
|
||||
.eq("user_id", userId)
|
||||
.eq("library_kind", kind)
|
||||
.select("*")
|
||||
.single();
|
||||
if (error || !data)
|
||||
return void res.status(404).json({ detail: "Folder not found" });
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
// DELETE /library/:kind/folders/:folderId
|
||||
libraryRouter.delete("/:kind/folders/:folderId", requireAuth, async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const kind = normalizeLibraryKind(req.params.kind);
|
||||
if (!kind) return void res.status(404).json({ detail: "Library not found" });
|
||||
|
||||
const { folderId } = req.params;
|
||||
const db = createServerSupabase();
|
||||
const { data: allFolders, error: foldersError } = await db
|
||||
.from("library_folders")
|
||||
.select("id, parent_folder_id")
|
||||
.eq("user_id", userId)
|
||||
.eq("library_kind", kind);
|
||||
if (foldersError)
|
||||
return void res.status(500).json({ detail: foldersError.message });
|
||||
if (!(allFolders ?? []).some((folder) => folder.id === folderId)) {
|
||||
return void res.status(404).json({ detail: "Folder not found" });
|
||||
}
|
||||
|
||||
const childrenByParent = new Map<string, string[]>();
|
||||
for (const folder of allFolders ?? []) {
|
||||
const parentId = folder.parent_folder_id as string | null;
|
||||
if (!parentId) continue;
|
||||
const children = childrenByParent.get(parentId) ?? [];
|
||||
children.push(folder.id as string);
|
||||
childrenByParent.set(parentId, children);
|
||||
}
|
||||
|
||||
const folderIds = new Set<string>();
|
||||
const stack = [folderId];
|
||||
while (stack.length > 0) {
|
||||
const id = stack.pop()!;
|
||||
if (folderIds.has(id)) continue;
|
||||
folderIds.add(id);
|
||||
stack.push(...(childrenByParent.get(id) ?? []));
|
||||
}
|
||||
|
||||
let documentsInFolderQuery = db
|
||||
.from("documents")
|
||||
.select("id")
|
||||
.eq("user_id", userId)
|
||||
.is("project_id", null);
|
||||
documentsInFolderQuery =
|
||||
kind === "file"
|
||||
? documentsInFolderQuery.or("library_kind.eq.file,library_kind.is.null")
|
||||
: documentsInFolderQuery.eq("library_kind", kind);
|
||||
const { data: docs, error: docsError } = await documentsInFolderQuery.in(
|
||||
"library_folder_id",
|
||||
[...folderIds],
|
||||
);
|
||||
if (docsError) return void res.status(500).json({ detail: docsError.message });
|
||||
|
||||
const docIds = (docs ?? []).map((doc) => doc.id as string);
|
||||
const deleteDocsError = await deleteLibraryDocumentsAndVersionFiles(
|
||||
db,
|
||||
userId,
|
||||
kind,
|
||||
docIds,
|
||||
);
|
||||
if (deleteDocsError)
|
||||
return void res.status(500).json({ detail: deleteDocsError.message });
|
||||
|
||||
const { error } = await db
|
||||
.from("library_folders")
|
||||
.delete()
|
||||
.eq("id", folderId)
|
||||
.eq("user_id", userId)
|
||||
.eq("library_kind", kind);
|
||||
if (error) return void res.status(500).json({ detail: error.message });
|
||||
res.status(204).send();
|
||||
});
|
||||
|
||||
// PATCH /library/:kind/documents/:documentId/folder
|
||||
libraryRouter.patch(
|
||||
"/:kind/documents/:documentId/folder",
|
||||
requireAuth,
|
||||
async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const kind = normalizeLibraryKind(req.params.kind);
|
||||
if (!kind) return void res.status(404).json({ detail: "Library not found" });
|
||||
|
||||
const { documentId } = req.params;
|
||||
const { folder_id } = req.body as { folder_id: string | null };
|
||||
const db = createServerSupabase();
|
||||
|
||||
if (folder_id) {
|
||||
const folder = await loadLibraryFolder(db, userId, kind, folder_id);
|
||||
if (!folder)
|
||||
return void res.status(404).json({ detail: "Folder not found" });
|
||||
}
|
||||
|
||||
let moveQuery = db
|
||||
.from("documents")
|
||||
.update({
|
||||
library_folder_id: folder_id ?? null,
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq("id", documentId)
|
||||
.eq("user_id", userId)
|
||||
.is("project_id", null);
|
||||
moveQuery =
|
||||
kind === "file"
|
||||
? moveQuery.or("library_kind.eq.file,library_kind.is.null")
|
||||
: moveQuery.eq("library_kind", kind);
|
||||
const { data, error } = await moveQuery
|
||||
.select("*")
|
||||
.single();
|
||||
if (error || !data)
|
||||
return void res.status(404).json({ detail: "Document not found" });
|
||||
res.json(mapLibraryDocument(data));
|
||||
},
|
||||
);
|
||||
|
||||
// PATCH /library/:kind/documents/:documentId
|
||||
libraryRouter.patch(
|
||||
"/:kind/documents/:documentId",
|
||||
requireAuth,
|
||||
async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const kind = normalizeLibraryKind(req.params.kind);
|
||||
if (!kind) return void res.status(404).json({ detail: "Library not found" });
|
||||
|
||||
const { documentId } = req.params;
|
||||
const db = createServerSupabase();
|
||||
let docQuery = db
|
||||
.from("documents")
|
||||
.select("id, current_version_id")
|
||||
.eq("id", documentId)
|
||||
.eq("user_id", userId)
|
||||
.is("project_id", null);
|
||||
docQuery =
|
||||
kind === "file"
|
||||
? docQuery.or("library_kind.eq.file,library_kind.is.null")
|
||||
: docQuery.eq("library_kind", kind);
|
||||
const { data: doc } = await docQuery.single();
|
||||
if (!doc) return void res.status(404).json({ detail: "Document not found" });
|
||||
|
||||
const active = doc.current_version_id
|
||||
? await db
|
||||
.from("document_versions")
|
||||
.select("filename")
|
||||
.eq("id", doc.current_version_id)
|
||||
.eq("document_id", documentId)
|
||||
.single()
|
||||
: null;
|
||||
const currentName =
|
||||
typeof active?.data?.filename === "string" && active.data.filename.trim()
|
||||
? active.data.filename.trim()
|
||||
: "Untitled document";
|
||||
const filename = normalizeDocumentFilename(req.body?.filename, currentName);
|
||||
if (!filename)
|
||||
return void res.status(400).json({ detail: "filename is required" });
|
||||
|
||||
let updateQuery = db
|
||||
.from("documents")
|
||||
.update({ updated_at: new Date().toISOString() })
|
||||
.eq("id", documentId)
|
||||
.eq("user_id", userId)
|
||||
.is("project_id", null);
|
||||
updateQuery =
|
||||
kind === "file"
|
||||
? updateQuery.or("library_kind.eq.file,library_kind.is.null")
|
||||
: updateQuery.eq("library_kind", kind);
|
||||
const { data: updated, error } = await updateQuery
|
||||
.select("*")
|
||||
.single();
|
||||
if (error || !updated)
|
||||
return void res.status(404).json({ detail: "Document not found" });
|
||||
|
||||
if (doc.current_version_id) {
|
||||
await db
|
||||
.from("document_versions")
|
||||
.update({ filename })
|
||||
.eq("id", doc.current_version_id)
|
||||
.eq("document_id", documentId);
|
||||
}
|
||||
|
||||
res.json(mapLibraryDocument({ ...updated, filename }));
|
||||
},
|
||||
);
|
||||
|
|
@ -453,7 +453,11 @@ projectsRouter.post(
|
|||
// Standalone → assign project_id
|
||||
const { data: updated, error } = await db
|
||||
.from("documents")
|
||||
.update({ project_id: projectId, updated_at: new Date().toISOString() })
|
||||
.update({
|
||||
project_id: projectId,
|
||||
library_folder_id: null,
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq("id", documentId)
|
||||
.select("*")
|
||||
.single();
|
||||
|
|
|
|||
|
|
@ -997,6 +997,29 @@ tabularRouter.delete(
|
|||
},
|
||||
);
|
||||
|
||||
// PATCH /tabular-review/:reviewId/chats/:chatId — rename a chat
|
||||
tabularRouter.patch(
|
||||
"/:reviewId/chats/:chatId",
|
||||
requireAuth,
|
||||
async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const { chatId } = req.params;
|
||||
const title =
|
||||
typeof req.body?.title === "string" ? req.body.title.trim() : "";
|
||||
if (!title)
|
||||
return void res.status(400).json({ detail: "Title is required" });
|
||||
const db = createServerSupabase();
|
||||
// Owner-only rename — mirrors the delete rule above.
|
||||
const { error } = await db
|
||||
.from("tabular_review_chats")
|
||||
.update({ title: title.slice(0, 200) })
|
||||
.eq("id", chatId)
|
||||
.eq("user_id", userId);
|
||||
if (error) return void res.status(500).json({ detail: error.message });
|
||||
res.status(204).send();
|
||||
},
|
||||
);
|
||||
|
||||
// GET /tabular-review/:reviewId/chats/:chatId/messages — messages for a single chat
|
||||
tabularRouter.get(
|
||||
"/:reviewId/chats/:chatId/messages",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue