mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
feat(documents): add a document upload token for imports from other systems
A narrow `documents:write` token, minted cookie-only at
`POST /api/tokens/documents` and accepted by the vault upload route alone.
A scoped caller draws on its own hourly bucket per token
(`DOCUMENT_UPLOAD_LIMIT_PER_HOUR`, default 120) and gets a receipt
`{ id, duplicate }` instead of the stored row.
Uploads take optional `sourceSystem` / `sourceId` fields. The source key is
unique per account across live and deleted documents, and the tombstone
purge carries it to a small ledger, so re-sending a document the owner
deleted answers 200 `deleted: true` and stores nothing. `aiRead=defer`
indexes locally and skips the summary and lab staging.
The detail sheet shows where an imported document came from; backup and
restore carry both fields; Delete All Data clears the ledger.
Refs #1038, #938
This commit is contained in:
1 parent
af65f01fec
commit
55a32ff7d1
45 files changed
+2261
-96
No files matched your search
@@ -37,6 +37,8 @@ function doc(overrides: Partial<InboundDocumentDto> = {}): InboundDocumentDto {
|
||||
lastIndexAttemptAt: null,
|
||||
lastIndexOutcome: null,
|
||||
hasThumbnail: false,
|
||||
sourceSystem: null,
|
||||
sourceId: null,
|
||||
createdAt: "2025-10-05T08:00:00.000Z",
|
||||
updatedAt: "2025-10-05T08:00:00.000Z",
|
||||
...overrides,
|
||||
|
||||
@@ -89,6 +89,8 @@ function doc(
|
||||
lastIndexAttemptAt: null,
|
||||
lastIndexOutcome: null,
|
||||
hasThumbnail: false,
|
||||
sourceSystem: null,
|
||||
sourceId: null,
|
||||
createdAt: "2026-08-01T10:00:00.000Z",
|
||||
updatedAt: "2026-08-01T10:00:00.000Z",
|
||||
facts: [fact()],
|
||||
|
||||
@@ -57,6 +57,8 @@ function doc(id: string, title: string): InboundDocumentDto {
|
||||
lastIndexAttemptAt: null,
|
||||
lastIndexOutcome: null,
|
||||
hasThumbnail: false,
|
||||
sourceSystem: null,
|
||||
sourceId: null,
|
||||
createdAt: "2025-10-05T08:00:00.000Z",
|
||||
updatedAt: "2025-10-05T08:00:00.000Z",
|
||||
};
|
||||
|
||||
@@ -45,6 +45,8 @@ function doc(overrides: Partial<InboundDocumentDto> = {}): InboundDocumentDto {
|
||||
lastIndexAttemptAt: null,
|
||||
lastIndexOutcome: null,
|
||||
hasThumbnail: false,
|
||||
sourceSystem: null,
|
||||
sourceId: null,
|
||||
createdAt: "2026-03-11T08:00:00.000Z",
|
||||
updatedAt: "2026-03-11T08:00:00.000Z",
|
||||
...overrides,
|
||||
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
INBOUND_DOCUMENT_KINDS,
|
||||
type DocumentSourceSystemValue,
|
||||
type DocumentSuggestionDto,
|
||||
type DocumentSummaryMode,
|
||||
type InboundDocumentDetailDto,
|
||||
@@ -698,6 +699,14 @@ export function DocumentDetailSheet({
|
||||
: null;
|
||||
|
||||
const title = doc?.title ?? doc?.filename ?? t("documents.card.untitled");
|
||||
// The product names are proper nouns and stay untranslated; only "another
|
||||
// system" is prose.
|
||||
const sourceSystemName = (system: DocumentSourceSystemValue): string =>
|
||||
system === "PAPERLESS"
|
||||
? "Paperless-ngx"
|
||||
: system === "PAPRA"
|
||||
? "Papra"
|
||||
: t("documents.detail.sourceOther");
|
||||
const Icon = doc ? DOCUMENT_KIND_ICONS[doc.kind] : null;
|
||||
// v1.37.0 — the ONE eager fetch of the original, and only for a document
|
||||
// that is going to render it.
|
||||
@@ -1167,6 +1176,25 @@ export function DocumentDetailSheet({
|
||||
})}
|
||||
{doc.filename ? ` · ${doc.filename}` : ""}
|
||||
</p>
|
||||
|
||||
{/* Where an imported document came from (#1038). Meta, so
|
||||
muted like the line above; the id is what the person looks
|
||||
up in the other system. */}
|
||||
{doc.sourceSystem ? (
|
||||
<p
|
||||
className="text-muted-foreground text-xs break-all"
|
||||
data-slot="document-detail-provenance"
|
||||
>
|
||||
{doc.sourceId
|
||||
? t("documents.detail.importedFromWithId", {
|
||||
system: sourceSystemName(doc.sourceSystem),
|
||||
id: doc.sourceId,
|
||||
})
|
||||
: t("documents.detail.importedFrom", {
|
||||
system: sourceSystemName(doc.sourceSystem),
|
||||
})}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Reference in new issue
Block a user