chore: add project documentation, restrict email import permissions, and set date format in InboxPage
Build and Push Multi-Platform Images / build-and-push (push) Successful in 32s

This commit is contained in:
2026-05-09 11:06:11 +02:00
parent 195ebc793e
commit 3f2b3a7af4
3 changed files with 136 additions and 1 deletions
+134
View File
@@ -0,0 +1,134 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Paperless Manager is a document automation platform that extends [Paperless-NGX](https://github.com/paperless-ngx/paperless-ngx). It provides:
- Scanner inbox management with PDF processing (splitting, rotation, barcode detection)
- Email import with attachment extraction
- Rule-based postprocessing (tag, export, send mail)
- OCR via Ollama (vision model)
- Label printing agent with SSE-based job queue
UI labels and comments are in **German**.
## Structure
```
paperless-backend/ # NestJS API (port 3100)
paperless-frontend/ # React 19 + Ant Design + Vite
docker-compose.yml # Production (backend + frontend/nginx + MySQL)
.env.example # All required environment variables
```
## Commands
**Backend** (in `paperless-backend/`):
```bash
npm run start:dev # Dev server with watch mode
npm run build # Compile TypeScript
npm run test # Jest unit tests
npm run test:e2e # End-to-end tests
npm run lint # ESLint with auto-fix
```
**Frontend** (in `paperless-frontend/`):
```bash
npm run dev # Vite dev server (proxies /api to backend)
npm run build # TypeScript check + Vite build
npm run lint # ESLint
```
## Backend Architecture
### Module Overview
| Module | Purpose |
|--------|---------|
| `auth` | JWT (OIDC/Authentik) + API Key guards, permission system |
| `database` | TypeORM entities + MySQL config (synchronize: true) |
| `inbox` | Scanner document management (list, preview, rotate, split) |
| `paperless` | Paperless-NGX REST API client |
| `preprocessing` | PDF→images, OCR, QR extraction, task creation |
| `postprocessing` | Rule engine: filter conditions → actions |
| `email` | IMAP intake, PDF conversion, correspondent mapping |
| `email-download` | External email retrieval, ZUGFeRD invoice parsing |
| `barcode` | QR/barcode detection, template-based split actions |
| `label-print-agent` | SVG label rendering, RxJS-based print job queue + SSE |
| `inbox-postprocessor` | Applies edits/splits to PDFs, variable substitution |
| `scanner` | chokidar file system watcher for scan directory |
| `settings` | Global configuration |
| `user-settings` | Per-user SMTP and preferences |
### Authentication & Permissions
Global guards apply to all routes:
1. `JwtOrApiKeyGuard` — validates Bearer JWT (OIDC) or `X-API-Key` header
2. `PermissionsGuard` — enforces `@RequirePermissions()` decorator
Decorate controllers/handlers with:
```typescript
@RequirePermissions(Permission.VIEW_SCANNER)
```
Permissions map from OIDC groups (`PM_Admin`, `PM_Belege`, etc.) to the `Permission` enum in `src/auth/permissions.enum.ts`.
Use `@Public()` to bypass auth guards entirely.
### Database
- TypeORM with MySQL 8+, UTF8MB4, `synchronize: true` (schema auto-migrates)
- 23 entities in `src/database/entities/`
- JSON columns use transformers to normalize empty arrays/objects to `null`
- Key entities: `InboxDocument`, `Task`, `Email`, `Attachment`, `Postprocessing`, `BarcodeTemplate`, `LabelPrintJob`, `ApiKey`, `Setting`
### Document Processing Pipeline
**Preprocessing** (`preprocessing/document-pipeline.service.ts`):
1. PDF → PNG page images (200 DPI via pdf-lib + sharp)
2. QR code extraction from page 1 (jsqr)
3. OCR via Ollama (llava vision model)
4. Auto-generated internal document number (YYYY-000001)
5. DB task entry creation, archive original (GoBD compliance)
**Postprocessing** (`postprocessing/postprocessing.service.ts`):
1. Load rules ordered by priority with filter conditions (JSON)
2. Evaluate filters (field operators: eq, contains, in, etc.)
3. Execute actions: tag, update field, send mail, export WebDAV
4. Log results; apply error tag on failure; stop if `NoFurther` flag set
**Inbox Postprocessor** (`inbox-postprocessor/`):
- Applies virtual edits (page deletions, rotations, splits) stored in DB to original PDF
- Resolves variable templates from barcode/task data
- Returns PDF buffer for upload to Paperless-NGX
### Label Print Agent
- Uses RxJS `Subject` (`newJob$`) to stream print jobs via SSE to connected agents
- Jobs are locked for 5 minutes to prevent race conditions
- SVG templates rendered to PNG via `@resvg/resvg-js`
- API documented in `docs/BACKEND_API.md`
## Frontend Architecture
- **Auth**: OIDC flow via `oidc-client-ts`, `AuthContext` provides user identity and tokens
- **API layer**: Axios-based client methods in `src/api/` (one file per domain)
- **Routing**: React Router v7 in `src/App.tsx`
- **Key components**: `DocumentEditModal`, `PdfSplitViewer`, `WysiwygEditor` (TipTap), `BarcodePositioner`
- During dev, Vite proxies `/api` to `localhost:3100`
## Environment Variables
Key variables (see `.env.example` for full list):
| Variable | Purpose |
|----------|---------|
| `DB_*` | MySQL connection |
| `PAPERLESS_URL`, `PAPERLESS_TOKEN` | Paperless-NGX API |
| `OIDC_ISSUER`, `OIDC_CLIENT_ID` | OIDC provider (Authentik) |
| `OLLAMA_URL`, `OLLAMA_MODEL` | OCR service |
| `SCANNER_WATCH_DIR`, `SCANNER_ARCHIVE_DIR` | File system paths |
| `BELEGNUMMER_GET_URL`, `BELEGNUMMER_SET_URL` | External invoice number API |
| `PORT` | Backend port (default 3100) |
| `VITE_API_URL` | Override API URL in frontend (dev only) |
@@ -132,7 +132,7 @@ export class EmailImportController {
// --- Final Import --- // --- Final Import ---
@Post('execute') @Post('execute')
@RequirePermissions(Permission.MANAGE_ALL) @RequirePermissions(Permission.VIEW_MAIL)
async executeImport(@Body() importData: any) { async executeImport(@Body() importData: any) {
try { try {
const result = await this.importService.executeImport(importData); const result = await this.importService.executeImport(importData);
@@ -434,6 +434,7 @@ export default function InboxPage() {
{field.type === 'date' ? ( {field.type === 'date' ? (
<DatePicker <DatePicker
style={{ width: '100%' }} style={{ width: '100%' }}
format="DD.MM.YYYY"
value={fieldValues[field.name] ? dayjs(fieldValues[field.name]) : null} value={fieldValues[field.name] ? dayjs(fieldValues[field.name]) : null}
onChange={(d) => onChange={(d) =>
setFieldValues((prev) => ({ setFieldValues((prev) => ({