diff --git a/src/main/api-server/routes-files.ts b/src/main/api-server/routes-files.ts index bae02baf..978f37e0 100644 --- a/src/main/api-server/routes-files.ts +++ b/src/main/api-server/routes-files.ts @@ -2,19 +2,6 @@ import { z } from "zod"; import { IMPORTANT_DOC_TYPE_SET } from "@shared/document-categories"; import { requireParam, type AgentApiServer } from "./server-core"; -type DocumentRow = { - id: string; companyId: string; type: string; title: string; content: string; - authorAgentId: string; reviewerAgentId: string | null; projectId: string | null; - goalId: string | null; parentDocId: string | null; version: number; status: string; - tagsJson: string; latestRevisionId: string | null; createdAt: string; updatedAt: string; -}; - -type DocumentLookupRow = { - id: string; companyId: string; type: string; title: string; content: string; - authorAgentId: string; reviewerAgentId: string | null; projectId: string | null; - goalId: string | null; version: number; status: string; tagsJson: string; -}; - const createDocumentSchema = z.object({ type: z.string().optional(), title: z.string().optional(), @@ -171,10 +158,7 @@ export function registerFileRoutes(server: AgentApiServer) { server.json(res, 403, { error: "Company mismatch" }); return; } - const docs = db.queryAll( - "select id, company_id as companyId, type, title, content, author_agent_id as authorAgentId, reviewer_agent_id as reviewerAgentId, project_id as projectId, goal_id as goalId, parent_doc_id as parentDocId, version, status, tags_json as tagsJson, latest_revision_id as latestRevisionId, created_at as createdAt, updated_at as updatedAt from documents where company_id = ? order by updated_at desc", - token.companyId, - ).map((d) => ({ ...d, latestRevisionId: d.latestRevisionId ?? null })); + const docs = db.listDocuments(token.companyId); server.json(res, 200, docs); }); @@ -188,11 +172,7 @@ export function registerFileRoutes(server: AgentApiServer) { server.json(res, 400, { error: "Missing documentId" }); return; } - const docRows = db.queryAll( - "select id, company_id as companyId, type, title, content, author_agent_id as authorAgentId, reviewer_agent_id as reviewerAgentId, project_id as projectId, goal_id as goalId, version, status, tags_json as tagsJson from documents where id = ? and company_id = ? limit 1", - documentId, token.companyId, - ); - const doc = docRows[0] ?? null; + const doc = db.getDocumentForUpdate(documentId, token.companyId); if (!doc) { server.json(res, 404, { error: "Document not found in this company" }); return; diff --git a/src/main/database.ts b/src/main/database.ts index d2b1f2dd..d244f98b 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -83,6 +83,8 @@ export class AppDatabase extends AppDatabaseBase { saveDocument(input: { id?: string | null; companyId: string; type: string; title: string; content?: string; authorAgentId?: string | null; reviewerAgentId?: string | null; projectId?: string | null; goalId?: string | null; parentDocId?: string | null; version?: number; status?: string; tagsJson?: string; sourceRunId?: string | null }) { return this.documentsMod.saveDocument(input); } listImportantDocsForReview(input: { companyId: string; types: readonly string[]; authorAgentId: string | null; taskId: string; limit?: number }) { return this.documentsMod.listImportantDocsForReview(input); } + listDocuments(companyId: string) { return this.documentsMod.listDocuments(companyId); } + getDocumentForUpdate(documentId: string, companyId: string) { return this.documentsMod.getDocumentForUpdate(documentId, companyId); } updateDocumentStatus(id: string, companyId: string, status: string) { this.documentsMod.updateDocumentStatus(id, companyId, status); } deleteDocument(id: string, companyId: string) { this.documentsMod.deleteDocument(id, companyId); } createDocumentRevision(input: { documentId: string; companyId: string; body: string; changeSummary?: string | null; createdByAgentId?: string | null; createdByUserId?: string | null }) { return this.documentsMod.createDocumentRevision(input); } diff --git a/src/main/database/documents.ts b/src/main/database/documents.ts index d0409ac3..dbb028f8 100644 --- a/src/main/database/documents.ts +++ b/src/main/database/documents.ts @@ -4,6 +4,49 @@ import type { AgentRecord } from "@shared/types"; import type { ReadAll, SqliteRow, Transaction } from "./helpers"; import { ensureId, nowIso, parseJson } from "./helpers"; +/** + * Document row shape returned by listDocuments — used by + * GET /api/companies/:companyId/documents. Mirrors the historical inline + * SQL projection so HTTP response bodies stay byte-stable for clients. + */ +export type DocumentListRow = { + id: string; + companyId: string; + type: string; + title: string; + content: string; + authorAgentId: string; + reviewerAgentId: string | null; + projectId: string | null; + goalId: string | null; + parentDocId: string | null; + version: number; + status: string; + tagsJson: string; + latestRevisionId: string | null; + createdAt: string; + updatedAt: string; +}; + +/** + * Document lookup row used by the PATCH document handler — narrower + * projection without parent_doc_id / latest_revision_id / timestamps. + */ +export type DocumentLookupRow = { + id: string; + companyId: string; + type: string; + title: string; + content: string; + authorAgentId: string; + reviewerAgentId: string | null; + projectId: string | null; + goalId: string | null; + version: number; + status: string; + tagsJson: string; +}; + export function buildDocumentMethods( getDb: () => DatabaseSync, readAll: ReadAll, @@ -31,6 +74,33 @@ export function buildDocumentMethods( return id; }, + /** + * Returns all documents for a company, ordered by updated_at desc. + * Used by GET /api/companies/:companyId/documents. + */ + listDocuments(companyId: string): DocumentListRow[] { + return readAll( + "select id, company_id as companyId, type, title, content, author_agent_id as authorAgentId, reviewer_agent_id as reviewerAgentId, project_id as projectId, goal_id as goalId, parent_doc_id as parentDocId, version, status, tags_json as tagsJson, latest_revision_id as latestRevisionId, created_at as createdAt, updated_at as updatedAt from documents where company_id = ? order by updated_at desc", + companyId, + ).map((d) => ({ ...d, latestRevisionId: d.latestRevisionId ?? null })); + }, + + /** + * Returns the lookup row needed for PATCH /api/companies/:companyId/documents/:documentId. + * Returns null when the document is missing or belongs to another company. + */ + getDocumentForUpdate( + documentId: string, + companyId: string, + ): DocumentLookupRow | null { + const row = getDb() + .prepare( + "select id, company_id as companyId, type, title, content, author_agent_id as authorAgentId, reviewer_agent_id as reviewerAgentId, project_id as projectId, goal_id as goalId, version, status, tags_json as tagsJson from documents where id = ? and company_id = ? limit 1", + ) + .get(documentId, companyId) as DocumentLookupRow | undefined; + return row ?? null; + }, + updateDocumentStatus(id: string, companyId: string, status: string): void { getDb().prepare( "UPDATE documents SET status = ?, updated_at = ? WHERE id = ? AND company_id = ?",