refactor: replace direct Paperless upload with client-side document download functionality
Build and Push Multi-Platform Images / build-and-push (push) Successful in 36s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 36s
This commit is contained in:
@@ -176,21 +176,18 @@ export class InboxController {
|
||||
await this.inboxService.updateSource(id, body.source, preferredUsername);
|
||||
}
|
||||
|
||||
@Post(':id/save-to-paperless')
|
||||
@HttpCode(204)
|
||||
async saveToPaperless(
|
||||
@Get(':id/download')
|
||||
async download(
|
||||
@Param('id') id: string,
|
||||
@Body() body: {
|
||||
title: string;
|
||||
date?: string;
|
||||
documentTypeId?: number;
|
||||
correspondentId?: number;
|
||||
tagIds?: number[];
|
||||
},
|
||||
@Request() req: any,
|
||||
): Promise<void> {
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
): Promise<StreamableFile> {
|
||||
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
|
||||
await this.inboxService.saveToPaperless(id, preferredUsername, body);
|
||||
const { buffer, filename } = await this.inboxService.getEditedPdfBuffer(id, preferredUsername);
|
||||
const { Readable } = await import('stream');
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
|
||||
return new StreamableFile(Readable.from(buffer));
|
||||
}
|
||||
|
||||
@Post(':id/send-email')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Client } from '../database/entities/client.entity';
|
||||
import { UserClient } from '../database/entities/user-client.entity';
|
||||
@@ -9,7 +9,6 @@ import { InboxService } from './inbox.service';
|
||||
import { InboxMigrationService } from './inbox-migration.service';
|
||||
import { BarcodeModule } from '../barcode/barcode.module';
|
||||
import { InboxPostprocessorModule } from '../inbox-postprocessor/inbox-postprocessor.module';
|
||||
import { PaperlessModule } from '../paperless/paperless.module';
|
||||
import { PostprocessingModule } from '../postprocessing/postprocessing.module';
|
||||
|
||||
@Module({
|
||||
@@ -17,7 +16,6 @@ import { PostprocessingModule } from '../postprocessing/postprocessing.module';
|
||||
TypeOrmModule.forFeature([Client, UserClient, InboxDocument]),
|
||||
BarcodeModule,
|
||||
InboxPostprocessorModule,
|
||||
forwardRef(() => PaperlessModule),
|
||||
PostprocessingModule,
|
||||
],
|
||||
controllers: [InboxController, ClientsController],
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
InboxDocument,
|
||||
type InboxSource,
|
||||
} from '../database/entities/inbox-document.entity';
|
||||
import { PaperlessService } from '../paperless/paperless.service';
|
||||
import { MailService } from '../postprocessing/mail.service';
|
||||
import { applyEditsToTemp, cleanupTemp } from '../inbox-postprocessor/edit-applier';
|
||||
|
||||
@@ -44,7 +43,6 @@ export class InboxService {
|
||||
private readonly pageCache: PageCacheService,
|
||||
@InjectRepository(InboxDocument)
|
||||
private readonly documentRepo: Repository<InboxDocument>,
|
||||
private readonly paperlessService: PaperlessService,
|
||||
private readonly mailService: MailService,
|
||||
) {}
|
||||
|
||||
@@ -255,28 +253,16 @@ export class InboxService {
|
||||
return this.barcodeScanner.scanRegion(doc, pdfPath, page, x, y, w, h);
|
||||
}
|
||||
|
||||
async saveToPaperless(
|
||||
async getEditedPdfBuffer(
|
||||
id: string,
|
||||
preferredUsername: string | null,
|
||||
opts: {
|
||||
title: string;
|
||||
date?: string;
|
||||
documentTypeId?: number;
|
||||
correspondentId?: number;
|
||||
tagIds?: number[];
|
||||
},
|
||||
): Promise<void> {
|
||||
): Promise<{ buffer: Buffer; filename: string }> {
|
||||
const { doc, pdfPath } = await this.resolveDocument(id, preferredUsername);
|
||||
let tmpPath: string | null = null;
|
||||
try {
|
||||
tmpPath = await applyEditsToTemp(doc, pdfPath);
|
||||
await this.paperlessService.uploadDocument(tmpPath, {
|
||||
title: opts.title,
|
||||
created: opts.date,
|
||||
documentType: opts.documentTypeId,
|
||||
correspondent: opts.correspondentId,
|
||||
tags: opts.tagIds,
|
||||
});
|
||||
const buffer = await fs.readFile(tmpPath);
|
||||
return { buffer, filename: doc.OriginalName };
|
||||
} finally {
|
||||
await cleanupTemp(tmpPath);
|
||||
}
|
||||
|
||||
@@ -107,17 +107,8 @@ export const inboxApi = {
|
||||
)
|
||||
.then((r) => r.data),
|
||||
|
||||
saveToPaperless: (
|
||||
id: string,
|
||||
body: {
|
||||
title: string;
|
||||
date?: string;
|
||||
documentTypeId?: number;
|
||||
correspondentId?: number;
|
||||
tagIds?: number[];
|
||||
},
|
||||
) =>
|
||||
api.post(`/api/inbox/${encodeURIComponent(id)}/save-to-paperless`, body).then(() => {}),
|
||||
downloadBlob: (id: string) =>
|
||||
api.get<Blob>(`/api/inbox/${encodeURIComponent(id)}/download`, { responseType: 'blob' }).then((r) => r.data),
|
||||
|
||||
sendEmail: (
|
||||
id: string,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button, DatePicker, Dropdown, Empty, Form, Input, Modal, Popconfirm, Select, Space, Spin, Steps, Tag, Tooltip, Typography, message } from 'antd';
|
||||
import { Button, Dropdown, Empty, Form, Input, Modal, Popconfirm, Space, Spin, Steps, Tag, Tooltip, Typography, message } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
@@ -25,7 +25,7 @@ import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import { inboxApi, type InboxBarcode, type InboxFile, type PostprocessActionResult } from '../api/inbox';
|
||||
import { paperlessApi, type PaperlessDocType, type PaperlessCorrespondent, type PaperlessTag } from '../api/paperless';
|
||||
import { paperlessApi } from '../api/paperless';
|
||||
|
||||
const ZOOM_MIN = 0.5;
|
||||
const ZOOM_MAX = 3;
|
||||
@@ -528,88 +528,6 @@ function PostprocessWizardModal({
|
||||
);
|
||||
}
|
||||
|
||||
interface SaveToPaperlessDialogProps {
|
||||
open: boolean;
|
||||
fileId: string;
|
||||
defaultTitle: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function SaveToPaperlessDialog({ open, fileId, defaultTitle, onClose }: SaveToPaperlessDialogProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [docTypes, setDocTypes] = useState<PaperlessDocType[]>([]);
|
||||
const [correspondents, setCorrespondents] = useState<PaperlessCorrespondent[]>([]);
|
||||
const [tags, setTags] = useState<PaperlessTag[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
form.resetFields();
|
||||
form.setFieldsValue({ title: defaultTitle });
|
||||
Promise.all([
|
||||
paperlessApi.getDocumentTypes(),
|
||||
paperlessApi.getCorrespondents(),
|
||||
paperlessApi.getTags(),
|
||||
]).then(([dt, co, tg]) => {
|
||||
setDocTypes(dt);
|
||||
setCorrespondents(co);
|
||||
setTags(tg);
|
||||
}).catch(() => {});
|
||||
}, [open, defaultTitle, form]);
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setSubmitting(true);
|
||||
await inboxApi.saveToPaperless(fileId, {
|
||||
title: values.title,
|
||||
date: values.date ? values.date.format('YYYY-MM-DD') : undefined,
|
||||
documentTypeId: values.documentTypeId,
|
||||
correspondentId: values.correspondentId,
|
||||
tagIds: values.tagIds,
|
||||
});
|
||||
message.success('Dokument wurde an Paperless übertragen');
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
if (err?.errorFields) return;
|
||||
message.error('Übertragung fehlgeschlagen');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title="Dokument in Paperless speichern"
|
||||
onCancel={onClose}
|
||||
onOk={handleOk}
|
||||
okText="Speichern"
|
||||
cancelText="Abbrechen"
|
||||
confirmLoading={submitting}
|
||||
destroyOnClose
|
||||
width={520}
|
||||
>
|
||||
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item name="title" label="Titel" rules={[{ required: true, message: 'Bitte Titel angeben' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="date" label="Datum">
|
||||
<DatePicker style={{ width: '100%' }} format="DD.MM.YYYY" />
|
||||
</Form.Item>
|
||||
<Form.Item name="documentTypeId" label="Dokumenttyp">
|
||||
<Select allowClear placeholder="Kein Typ" options={docTypes.map((d) => ({ value: d.id, label: d.name }))} />
|
||||
</Form.Item>
|
||||
<Form.Item name="correspondentId" label="Korrespondent">
|
||||
<Select allowClear placeholder="Kein Korrespondent" options={correspondents.map((c) => ({ value: c.id, label: c.name }))} />
|
||||
</Form.Item>
|
||||
<Form.Item name="tagIds" label="Tags">
|
||||
<Select mode="multiple" allowClear placeholder="Keine Tags" options={tags.map((t) => ({ value: t.id, label: t.name }))} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function TiptapToolbar({ editor }: { editor: ReturnType<typeof useEditor> }) {
|
||||
if (!editor) return null;
|
||||
@@ -720,7 +638,7 @@ export default function InboxDetailPage() {
|
||||
const [selectedPage, setSelectedPage] = useState(1);
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [wizardOpen, setWizardOpen] = useState(false);
|
||||
const [saveDialogOpen, setSaveDialogOpen] = useState(false);
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [emailDialogOpen, setEmailDialogOpen] = useState(false);
|
||||
const [scanMode, setScanMode] = useState(false);
|
||||
const [scanning, setScanning] = useState(false);
|
||||
@@ -1087,7 +1005,8 @@ export default function InboxDetailPage() {
|
||||
<Dropdown.Button
|
||||
type="primary"
|
||||
icon={<DownOutlined />}
|
||||
disabled={documents.length === 0}
|
||||
disabled={documents.length === 0 || downloading}
|
||||
loading={downloading}
|
||||
onClick={() => setWizardOpen(true)}
|
||||
menu={{
|
||||
items: [
|
||||
@@ -1095,7 +1014,19 @@ export default function InboxDetailPage() {
|
||||
{ key: 'email', label: 'Als E-Mail-Anhang versenden', icon: <MailOutlined /> },
|
||||
] as MenuProps['items'],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'save') setSaveDialogOpen(true);
|
||||
if (key === 'save') {
|
||||
setDownloading(true);
|
||||
inboxApi.downloadBlob(file.id).then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = file.name;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}).catch(() => {
|
||||
message.error('Download fehlgeschlagen');
|
||||
}).finally(() => setDownloading(false));
|
||||
}
|
||||
if (key === 'email') setEmailDialogOpen(true);
|
||||
},
|
||||
}}
|
||||
@@ -1523,12 +1454,6 @@ export default function InboxDetailPage() {
|
||||
onClose={() => setWizardOpen(false)}
|
||||
onDeleted={() => navigate('/inbox')}
|
||||
/>
|
||||
<SaveToPaperlessDialog
|
||||
open={saveDialogOpen}
|
||||
fileId={file.id}
|
||||
defaultTitle={file.name}
|
||||
onClose={() => setSaveDialogOpen(false)}
|
||||
/>
|
||||
<SendEmailDialog
|
||||
open={emailDialogOpen}
|
||||
fileId={file.id}
|
||||
|
||||
Reference in New Issue
Block a user