mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
fix(documents): close v1.25.1 library QA findings (backend)
Default documentDate at store time. A store-only upload no longer lands with a NULL filing date: when the client omits one, default it to the upload day so the column is always set on a stored row. The library list sorts, groups, and range-filters on the same effective date the UI shows, so undated uploads no longer mis-sort under nulls-last, break date-group headers, or fall through the range filter. The date stays user-editable and may be cleared back to NULL via PATCH. Block re-extraction once any fact is APPROVED. A partially-confirmed document stays EXTRACTED, which the CONFIRMED guard does not catch; re-extracting would delete the approved facts (severing the committed record's provenance) and re-stage them as PENDING, letting a second approval duplicate the committed lab / condition / medication. The extract route now refuses with 409 (documents.inbound.alreadyPartlyConfirmed) when approved facts exist; the user must finish reviewing or discard first. RFC 5987-encode the original-download filename. The Content-Disposition now carries both an ASCII filename= fallback and a filename*=UTF-8'' value so a non-ASCII name survives intact; the stored set is inline-safe by construction, so the dead attachment branch is removed. Add a light per-user rate-limit backstop to the original-read, PATCH, and DELETE routes (generous; self-DoS only — the routes are owner-scoped). Lean the list counts: tally facts for the whole page in one grouped count instead of materialising fact-id rows, and exclude REJECTED facts from factCount. Extend the documentDate index with deleted_at to cover the list scan. Document the deliberate plaintext-title PHI tradeoff at the column and the store site.
This commit is contained in:
1 parent
84543ad540
commit
eb1779d5c1
14 files changed
+317
-94
No files matched your search
@@ -25,7 +25,7 @@ import {
|
||||
INBOUND_DOCUMENT_STATUSES,
|
||||
} from "@/lib/validations/inbound-documents";
|
||||
|
||||
import { dataEnvelope, stdResponses } from "./shared";
|
||||
import { dataEnvelope, errorEnvelope, stdResponses } from "./shared";
|
||||
|
||||
const kindEnum = z.enum(INBOUND_DOCUMENT_KINDS);
|
||||
const statusEnum = z.enum(INBOUND_DOCUMENT_STATUSES);
|
||||
@@ -238,7 +238,8 @@ export const inboundDocumentPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
.optional()
|
||||
.meta({ description: "Optional category." }),
|
||||
documentDate: z.string().optional().meta({
|
||||
description: "Optional user filing date (YYYY-MM-DD).",
|
||||
description:
|
||||
"Optional user filing date (YYYY-MM-DD). Defaults to the upload day when omitted so the library sort/filter/display stay aligned.",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
@@ -352,7 +353,7 @@ export const inboundDocumentPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Documents"],
|
||||
summary: "View / download the original document",
|
||||
description:
|
||||
"Decrypts and serves the raw uploaded document (the bytes stored encrypted at rest under `InboundDocument.contentEncrypted`). Owner-scoped + gated on the opt-in `inboundDocuments` module. The response carries the stored `mimeType`: a PDF / image is served inline (`Content-Disposition: inline`) so the browser can render it; any other type downloads as an attachment. Fails closed (500) on a decrypt error — it never returns the ciphertext.",
|
||||
"Decrypts and serves the raw uploaded document (the bytes stored encrypted at rest under `InboundDocument.contentEncrypted`). Owner-scoped + gated on the opt-in `inboundDocuments` module. Uploads are constrained at the store path to the inline-safe set (JPEG / PNG / WebP / PDF), so the document is always served `Content-Disposition: inline` for in-tab rendering; a non-ASCII filename is carried via RFC 5987 `filename*`. Fails closed (500) on a decrypt error — it never returns the ciphertext.",
|
||||
parameters: [
|
||||
{
|
||||
name: "id",
|
||||
@@ -372,9 +373,6 @@ export const inboundDocumentPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
"image/*": {
|
||||
schema: { type: "string", format: "binary" },
|
||||
},
|
||||
"text/plain": {
|
||||
schema: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
@@ -421,6 +419,11 @@ export const inboundDocumentPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"409": {
|
||||
description:
|
||||
"Re-extraction refused: at least one fact on this document is already APPROVED. `meta.errorCode` = `documents.inbound.alreadyPartlyConfirmed`. Re-extracting would sever committed-record provenance and duplicate committed records, so the user must finish reviewing or discard the document first.",
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user