Title, Provider, tags from paperless
This commit is contained in:
21
src/index.ts
21
src/index.ts
@@ -365,6 +365,27 @@ app.get("/integrations/paperless/search", async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/integrations/paperless/documents/:documentId", async (req, res, next) => {
|
||||
if (!paperlessClient.isConfigured) {
|
||||
return res.status(503).json({ error: "Paperless integration not configured" });
|
||||
}
|
||||
|
||||
const documentId = parseId(req.params.documentId);
|
||||
if (!documentId) {
|
||||
return res.status(400).json({ error: "Invalid document id" });
|
||||
}
|
||||
|
||||
try {
|
||||
const document = await paperlessClient.getDocument(documentId);
|
||||
if (!document) {
|
||||
return res.status(404).json({ error: "Document not found" });
|
||||
}
|
||||
res.json(document);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/contracts", (req, res) => {
|
||||
const skip = Number(req.query.skip ?? 0);
|
||||
const limit = Math.min(Number(req.query.limit ?? 100), 500);
|
||||
|
||||
@@ -4,6 +4,12 @@ import { getRuntimeSettings } from "./runtimeSettings.js";
|
||||
|
||||
const logger = createLogger(config.logLevel);
|
||||
|
||||
type PaperlessDocument = Record<string, unknown>;
|
||||
|
||||
interface PaperlessCollectionResponse<T> {
|
||||
results?: T[];
|
||||
}
|
||||
|
||||
export class PaperlessClient {
|
||||
get isConfigured() {
|
||||
const { paperlessBaseUrl, paperlessToken } = getRuntimeSettings();
|
||||
@@ -31,12 +37,23 @@ export class PaperlessClient {
|
||||
return headers;
|
||||
}
|
||||
|
||||
async getDocument(documentId: number): Promise<Record<string, unknown> | null> {
|
||||
private async fetchJson<T>(url: URL): Promise<T> {
|
||||
const response = await fetch(url, { headers: this.getHeaders() });
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
logger.error(`Paperless API error ${response.status}: ${text}`);
|
||||
throw new Error(`Paperless request failed with status ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async getDocument(documentId: number): Promise<PaperlessDocument | null> {
|
||||
if (!this.isConfigured) {
|
||||
throw new Error("Paperless integration is not configured");
|
||||
}
|
||||
|
||||
const url = this.buildUrl(`/api/documents/${documentId}/`);
|
||||
const url = new URL(this.buildUrl(`/api/documents/${documentId}/`));
|
||||
url.searchParams.set("metadata", "true");
|
||||
const response = await fetch(url, { headers: this.getHeaders() });
|
||||
|
||||
if (response.status === 404) {
|
||||
@@ -49,7 +66,9 @@ export class PaperlessClient {
|
||||
throw new Error(`Paperless request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<Record<string, unknown>>;
|
||||
const document = (await response.json()) as PaperlessDocument;
|
||||
await this.enrichDocuments([document]);
|
||||
return document;
|
||||
}
|
||||
|
||||
async searchDocuments(query: string, page = 1): Promise<Record<string, unknown>> {
|
||||
@@ -60,16 +79,139 @@ export class PaperlessClient {
|
||||
const url = new URL(this.buildUrl("/api/documents/"));
|
||||
url.searchParams.set("query", query);
|
||||
url.searchParams.set("page", page.toString());
|
||||
url.searchParams.set("metadata", "true");
|
||||
|
||||
const response = await fetch(url, { headers: this.getHeaders() });
|
||||
const payload = await this.fetchJson<Record<string, unknown>>(url);
|
||||
const results = Array.isArray((payload as PaperlessCollectionResponse<PaperlessDocument>).results)
|
||||
? ((payload as PaperlessCollectionResponse<PaperlessDocument>).results as PaperlessDocument[])
|
||||
: [];
|
||||
await this.enrichDocuments(results);
|
||||
return payload;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
logger.error(`Paperless API error ${response.status}: ${text}`);
|
||||
throw new Error(`Paperless request failed with status ${response.status}`);
|
||||
async enrichDocuments(documents: PaperlessDocument[]): Promise<void> {
|
||||
if (!documents.length) return;
|
||||
|
||||
const correspondentIds = new Set<number>();
|
||||
const tagIds = new Set<number>();
|
||||
|
||||
for (const doc of documents) {
|
||||
if (typeof doc.correspondent === "number") {
|
||||
correspondentIds.add(doc.correspondent);
|
||||
}
|
||||
|
||||
const tags = Array.isArray(doc.tags) ? doc.tags : [];
|
||||
for (const tag of tags) {
|
||||
if (typeof tag === "number") {
|
||||
tagIds.add(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response.json() as Promise<Record<string, unknown>>;
|
||||
const [correspondents, tags] = await Promise.all([
|
||||
this.fetchCorrespondents(Array.from(correspondentIds)),
|
||||
this.fetchTags(Array.from(tagIds))
|
||||
]);
|
||||
|
||||
for (const doc of documents) {
|
||||
if (typeof doc.correspondent === "number" && correspondents.has(doc.correspondent)) {
|
||||
const correspondent = correspondents.get(doc.correspondent)!;
|
||||
doc.correspondent_name = correspondent.name ?? correspondent.slug ?? correspondent.title ?? correspondent.value ?? null;
|
||||
if (!doc.metadata || typeof doc.metadata !== "object") {
|
||||
doc.metadata = {};
|
||||
}
|
||||
(doc.metadata as Record<string, unknown>).correspondent_name = doc.correspondent_name;
|
||||
}
|
||||
|
||||
if (Array.isArray(doc.tags) && doc.tags.length > 0) {
|
||||
const tagNames: string[] = [];
|
||||
const tagObjects: Array<Record<string, unknown>> = [];
|
||||
for (const tag of doc.tags) {
|
||||
if (typeof tag === "number" && tags.has(tag)) {
|
||||
const tagData = tags.get(tag)!;
|
||||
const name =
|
||||
tagData.name ??
|
||||
tagData.slug ??
|
||||
tagData.label ??
|
||||
tagData.title ??
|
||||
tagData.value ??
|
||||
null;
|
||||
if (name && typeof name === "string") {
|
||||
tagNames.push(name);
|
||||
}
|
||||
tagObjects.push(tagData);
|
||||
} else if (typeof tag === "string") {
|
||||
tagNames.push(tag);
|
||||
} else if (tag && typeof tag === "object") {
|
||||
tagObjects.push(tag as Record<string, unknown>);
|
||||
}
|
||||
}
|
||||
if (tagNames.length > 0) {
|
||||
doc.tags__name = tagNames;
|
||||
if (!doc.metadata || typeof doc.metadata !== "object") {
|
||||
doc.metadata = {};
|
||||
}
|
||||
(doc.metadata as Record<string, unknown>).tag_names = tagNames;
|
||||
}
|
||||
if (tagObjects.length > 0) {
|
||||
doc.tag_details = tagObjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchCorrespondents(ids: number[]): Promise<Map<number, Record<string, unknown>>> {
|
||||
const map = new Map<number, Record<string, unknown>>();
|
||||
if (ids.length === 0) {
|
||||
return map;
|
||||
}
|
||||
|
||||
const chunks = this.chunkIds(ids);
|
||||
for (const chunk of chunks) {
|
||||
const url = new URL(this.buildUrl("/api/correspondents/"));
|
||||
url.searchParams.set("id__in", chunk.join(","));
|
||||
url.searchParams.set("page_size", "100");
|
||||
const payload = await this.fetchJson<PaperlessCollectionResponse<Record<string, unknown>>>(url);
|
||||
const results = Array.isArray(payload.results) ? payload.results : [];
|
||||
for (const item of results) {
|
||||
if (typeof item.id === "number") {
|
||||
map.set(item.id, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private async fetchTags(ids: number[]): Promise<Map<number, Record<string, unknown>>> {
|
||||
const map = new Map<number, Record<string, unknown>>();
|
||||
if (ids.length === 0) {
|
||||
return map;
|
||||
}
|
||||
|
||||
const chunks = this.chunkIds(ids);
|
||||
for (const chunk of chunks) {
|
||||
const url = new URL(this.buildUrl("/api/tags/"));
|
||||
url.searchParams.set("id__in", chunk.join(","));
|
||||
url.searchParams.set("page_size", "100");
|
||||
const payload = await this.fetchJson<PaperlessCollectionResponse<Record<string, unknown>>>(url);
|
||||
const results = Array.isArray(payload.results) ? payload.results : [];
|
||||
for (const item of results) {
|
||||
if (typeof item.id === "number") {
|
||||
map.set(item.id, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private chunkIds(ids: number[], size = 50): number[][] {
|
||||
const chunks: number[][] = [];
|
||||
for (let i = 0; i < ids.length; i += size) {
|
||||
chunks.push(ids.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user