Stage two of PDF support: the file can now get in and out. No viewer yet — that is next. A PDF is an attachment like any other, so it inherits everything already built: encryption at rest, naming by content hash, deduplication, the orphan sweep, and the authenticated retrieval route the client already caches offline. The note points at it through notes.pdf_attachment_id. Raw request body rather than the base64 JSON images travel in. A scanned book is tens of megabytes and base64 adds a third to that, on a path that carries nothing but the file. The bytes are checked for %PDF- before anything is written, so the declared content type is never trusted. Images and PDFs are limited separately — INKNOTE_MAX_IMAGE_MB (25) and INKNOTE_MAX_PDF_MB (200) — because a 25 MB picture is nearly always a mistake and a 25 MB document is not. Both configurable; the limits exist so a runaway upload cannot fill the disk, not to ration anything. The JSON body limit now follows the image limit instead of being a second number that could drift away from it. Verified against a real PDF: uploaded, stored with the INKENC01 header, returned byte-identical, deduplicated on a second upload, a GIF renamed to .pdf refused with 415, unauthenticated refused with 401. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
2.9 KiB
YAML
84 lines
2.9 KiB
YAML
# ─────────────────────────────────────────────
|
|
# InkNote — lokales Test-Setup (baut aus dem Quellcode)
|
|
#
|
|
# Prod-nah (nginx + gebautes Bundle) auf :3000
|
|
# docker compose -f docker-compose.dev.yml up -d --build
|
|
#
|
|
# Mit Hot-Reload-Dev-Server auf :5173 zusätzlich
|
|
# docker compose -f docker-compose.dev.yml --profile dev up -d --build
|
|
#
|
|
# docker compose -f docker-compose.dev.yml logs -f
|
|
# docker compose -f docker-compose.dev.yml down
|
|
#
|
|
# Secrets kommen aus .env (nicht committen!)
|
|
# Daten liegen unter ${INKNOTE_DATA_DIR:-./data} auf dem Host.
|
|
# ─────────────────────────────────────────────
|
|
|
|
services:
|
|
inknote-server:
|
|
build:
|
|
context: ./backend
|
|
dockerfile: Dockerfile
|
|
container_name: inknote-server-dev
|
|
restart: unless-stopped
|
|
environment:
|
|
- PORT=4000
|
|
- JWT_SECRET=${JWT_SECRET}
|
|
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
|
# Only used when the database has no users at all: creates the first admin.
|
|
- INKNOTE_ADMIN_USER=${INKNOTE_ADMIN_USER:-}
|
|
- INKNOTE_ADMIN_PASSWORD=${INKNOTE_ADMIN_PASSWORD:-}
|
|
- NODE_ENV=development
|
|
# Optional tuning; unset means the server's own defaults apply.
|
|
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-}
|
|
- INKNOTE_MAX_IMAGE_MB=${INKNOTE_MAX_IMAGE_MB:-}
|
|
- INKNOTE_MAX_PDF_MB=${INKNOTE_MAX_PDF_MB:-}
|
|
- MAX_VERSIONS_PER_NOTE=${MAX_VERSIONS_PER_NOTE:-}
|
|
- VERSION_RETENTION_DAYS=${VERSION_RETENTION_DAYS:-}
|
|
- VERSION_COALESCE_MINUTES=${VERSION_COALESCE_MINUTES:-}
|
|
# Bind mounts, not named volumes: the data sits in a plain directory on the
|
|
# Docker host where the host's own backup picks it up.
|
|
user: "${INKNOTE_UID:-1000}:${INKNOTE_GID:-1000}"
|
|
volumes:
|
|
- ${INKNOTE_DATA_DIR:-./data}/db:/app/data
|
|
- ${INKNOTE_DATA_DIR:-./data}/uploads:/app/uploads
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "http://localhost:4000/api/health"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
|
|
inknote-web:
|
|
build:
|
|
context: ./frontend
|
|
dockerfile: dockerfile
|
|
container_name: inknote-web-dev
|
|
restart: unless-stopped
|
|
ports:
|
|
- "3000:80"
|
|
depends_on:
|
|
inknote-server:
|
|
condition: service_healthy
|
|
|
|
# Vite mit Hot-Reload — nur mit "--profile dev"
|
|
inknote-vite:
|
|
image: node:20-alpine
|
|
container_name: inknote-vite-dev
|
|
profiles: ["dev"]
|
|
working_dir: /app
|
|
command: sh -c "npm install && npm run dev"
|
|
environment:
|
|
- VITE_API_TARGET=http://inknote-server:4000
|
|
volumes:
|
|
- ./frontend:/app
|
|
- inknote-dev-node-modules:/app/node_modules
|
|
ports:
|
|
- "5173:5173"
|
|
depends_on:
|
|
inknote-server:
|
|
condition: service_healthy
|
|
|
|
volumes:
|
|
inknote-dev-node-modules:
|