mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
feat(documents): search and import from Paperless-ngx and Papra on request
A person stores one connection per system (base address, Papra organization id, API token encrypted at rest) and can search it by document name with tag and date filters, then import picked documents one per request through the same ingest path as an upload: source-key answer before any download, content dedup, deleted stays deleted, quota, provenance, and the person's own AI reading setting. An import can link the document to the visit, condition or vaccination it was picked for. The operator lists the instances in DOCUMENT_SOURCE_ORIGINS; unset, the feature is off. Only listed origins are contacted, through safeFetch with the origin pinned, redirects refused, timeouts and byte caps. The routes are cookie-only and owner-only. The token is registered for rotation, removed by Delete all data, and not carried in backups.
This commit is contained in:
1 parent
89b18e001e
commit
fdc60f69c2
26 files changed
+2305
-1
No files matched your search
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Request schemas of the document picker (#1038): saving a connection,
|
||||
* searching a source, importing one picked document. No `userId` field in any
|
||||
* of them; the owner is always the authenticated session.
|
||||
*/
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { DOCUMENT_SOURCE_BASE_URL_MAX } from "@/lib/documents/sources/origins";
|
||||
import {
|
||||
DOCUMENT_PICKER_LINK_KINDS,
|
||||
DOCUMENT_PICKER_MAX_PAGE,
|
||||
DOCUMENT_PICKER_QUERY_MAX,
|
||||
} from "@/lib/documents/sources/types";
|
||||
import {
|
||||
documentSourceIdSchema,
|
||||
INBOUND_DOCUMENT_KINDS,
|
||||
} from "@/lib/validations/inbound-documents";
|
||||
|
||||
/** Printable ASCII without spaces: an id or a key, never prose. */
|
||||
const printable = /^[\x21-\x7e]+$/u;
|
||||
|
||||
const isoDate = z
|
||||
.string()
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/u, "Expected a YYYY-MM-DD date");
|
||||
|
||||
/** Longest API token or key a connection stores. */
|
||||
export const DOCUMENT_SOURCE_TOKEN_MAX = 512;
|
||||
|
||||
/**
|
||||
* Save a connection. `token` may be left out when a connection for the system
|
||||
* already exists: the stored one is kept, so the address or the organization
|
||||
* can change without typing the key again. The first save must carry it (the
|
||||
* route says so). `organizationId` is Papra's and required there.
|
||||
*/
|
||||
export const documentSourceSaveSchema = z.object({
|
||||
baseUrl: z.string().trim().min(1).max(DOCUMENT_SOURCE_BASE_URL_MAX),
|
||||
organizationId: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(128)
|
||||
.regex(printable, "Expected printable characters without spaces")
|
||||
.optional(),
|
||||
token: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(DOCUMENT_SOURCE_TOKEN_MAX)
|
||||
.regex(printable, "Expected printable characters without spaces")
|
||||
.optional(),
|
||||
});
|
||||
export type DocumentSourceSaveInput = z.infer<typeof documentSourceSaveSchema>;
|
||||
|
||||
export const documentSourceSearchSchema = z
|
||||
.object({
|
||||
q: z.string().trim().max(DOCUMENT_PICKER_QUERY_MAX).default(""),
|
||||
tag: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(64)
|
||||
.regex(printable, "Expected printable characters without spaces")
|
||||
.optional(),
|
||||
from: isoDate.optional(),
|
||||
to: isoDate.optional(),
|
||||
page: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(DOCUMENT_PICKER_MAX_PAGE)
|
||||
.default(1),
|
||||
})
|
||||
.refine((v) => !v.from || !v.to || v.from <= v.to, {
|
||||
path: ["to"],
|
||||
message: "The end date is before the start date",
|
||||
});
|
||||
|
||||
export const documentSourceImportSchema = z.object({
|
||||
sourceId: documentSourceIdSchema,
|
||||
kind: z.enum(INBOUND_DOCUMENT_KINDS).optional(),
|
||||
/** The record the picker was opened from, to link the document to. */
|
||||
link: z
|
||||
.object({
|
||||
kind: z.enum(DOCUMENT_PICKER_LINK_KINDS),
|
||||
id: z.string().trim().min(1).max(64),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
export type DocumentSourceImportInput = z.infer<
|
||||
typeof documentSourceImportSchema
|
||||
>;
|
||||
@@ -701,7 +701,7 @@ export const DOCUMENT_SOURCE_ID_MAX = 128;
|
||||
* The document's id in its source system. Printable ASCII only: it is a key,
|
||||
* not prose, and it is echoed on the detail sheet.
|
||||
*/
|
||||
const documentSourceIdSchema = z
|
||||
export const documentSourceIdSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
|
||||
Reference in new issue
Block a user