6.3 KiB
6.3 KiB
#032 M19 – Anhänge und Fotos an Schritten
Status: erledigt Im Tracker: #52 Meilenstein: v0.2.0 Labels: phase-2, backend, frontend, infra Geschätzt: 10 h Tatsächlich: 3 h 05 min
Ziel
„Seriennummer fotografiert“, „Typenschild“, „Zustand bei Übergabe“, „Rechnung abgelegt“ — manches lässt sich nicht abhaken, es muss gezeigt werden. Ein Schritt soll Dateien und Fotos aufnehmen können, am Handy direkt aus der Kamera.
Zu entscheiden
- Dateien im Dateisystem, Metadaten in der Datenbank — Empfehlung. Ein Pfad aus der
Umgebung (
ANHAENGE_PFAD), darunter nach Kunde und Lauf gegliedert. Gegenbyteain Postgres spricht die Sicherung: einpg_dumpmit Fotos ist nach einem Jahr keinpg_dumpmehr, das jemand einspielt. Gegen S3 spricht, dass diese Anwendung selbst gehostet läuft und keinen Objektspeicher voraussetzen soll. Folge, die mitgeplant werden muss: der Compose-Stand und die native Installation brauchen ein Volume, unddocs/betrieb/sicherung.mdmuss beides nennen. Eine Sicherung, die nur die Datenbank mitnimmt, verliert ab hier Daten — das ist genau die Art Zusage, die dieses Projekt sonst teuer bezahlt hat. - Keine Verkleinerung auf dem Server im ersten Schritt. Ein Handyfoto hat heute 3–6 MB; eine Obergrenze (etwa 10 MB je Datei, 20 Dateien je Lauf) ist ehrlicher als eine Umwandlung, die Metadaten verliert und eine Bildbibliothek ins Projekt zieht.
Aufgaben
- Modell
Anhang:schrittergebnisId,kundeId, Dateiname, Medientyp, Größe, Prüfsumme,erstelltVonId,erstelltAm; Datei daneben im Dateisystem - Hochladen über einen Route Handler mit
rechtErforderlich— kein Weg daran vorbei, und jeder Abruf prüft Recht und Mandant, auch der des Bildes selbst - Grenzen: Medientypen auf Bilder und PDF, Größe je Datei und Anzahl je Lauf, beides in der Umgebung einstellbar; eine abgewiesene Datei nennt den Grund
- Am Handy direkt aus der Kamera (
capture="environment"), am Rechner aus dem Dateiwähler - Der Archiv-Schreibschutz gilt auch hier: an einem archivierten Lauf kommt kein Anhang mehr
dazu und keiner weg.
laufMussVeraenderbarSeinsitzt an genau einer Stelle — dort anschließen - Druckansicht: Bilder als Miniaturen mit Dateinamen, PDF nur als Zeile
docs/betrieb/sicherung.md,docker-compose.yml,deploy/install.sh: Volume, Rechte, Sicherung und Rückspielung- Anleitung Kapitel 02 und 08
Akzeptanzkriterien
- Test: Anhang an einem Schritt hochladen, in der Abarbeitung und im Druck sichtbar
- Test: an einem archivierten Lauf wird das Hochladen abgewiesen
- Test: ein Anhang eines fremden Kunden ist über seine Adresse nicht abrufbar
- Test: eine zu große Datei und ein falscher Medientyp werden mit Grund abgewiesen
- Test:
docker-compose.ymlhat ein Volume für die Anhänge, undsicherung.mdnennt es (wietests/betriebsstand.test.tses für die Datenbank schon prüft) - Sicherung und Rückspielung einmal wirklich gefahren, nicht nur beschrieben
Beim Umsetzen dazugekommen
- Hochgeladen wird über eine Server Action, nicht über einen eigenen Route Handler. Die Aufgabe
oben nannte einen Route Handler; der wäre eine zweite Tür mit eigener Rechteprüfung. Eine Server
Action prüft dasselbe Recht an derselben Stelle wie jede andere Schreiboperation am Lauf. Ein
Route Handler bleibt für das Ausliefern — dort braucht es eine Adresse, die ein
<img>holen kann, undsrc/app/api/anhaenge/[id]/route.tsruft selbstrechtErforderlich("lesen"), weil die Hülle unter(app)Route Handler nicht abdeckt. - Der Pfad entsteht aus der Kennung, nie aus dem Dateinamen.
<ANHAENGE_PFAD>/<kundeId>/<laufId>/<id>.<endung>. Der Name vom Browser wird trotzdem gesäubert, weil er in einer Antwortkopfzeile landet. - Kein SVG, obwohl es ein Bild ist: ein SVG ist ein Dokument, das Skripte ausführt. Darum eine
Liste erlaubter Medientypen und kein
image/*. - Reihenfolge beim Anlegen: prüfen, Datei schreiben (
flag: "wx"), dann die Zeile — und bei einem Fehler die Datei wieder weg. Eine Datei ohne Zeile findet niemand; eine Zeile ohne Datei ist ein Anhang, der beim Öffnen fehlschlägt. Beim Entfernen umgekehrt: erst die Zeile, dann die Datei. - Zwei Felder
dateiin einem Formular — undFormData.getnahm das falsche. „Foto“ und „Datei“ stehen im selben Formular, abgeschickt wird immer beides, und das leere Feld kam zuerst: wer „Datei“ wählte, bekam „Keine Datei gewählt“, während „Foto“ ging. Neunzehn grüne Dienst-Tests sahen davon nichts, weil kein Dienst-Test zwei Felder schickt. Gefunden hat es erst die Klickprobe am gebauten Stand. Die Regel liegt jetzt als reine FunktiongewaehlteDatei()insrc/lib, undtests/e2e/anhaenge.spec.tslädt bewusst über den zweiten Knopf hoch. Gegenprobe gefahren: mitgetstattgetAllwird der Ablauf rot. docs/betrieb/sicherung.mdwar nicht nur unvollständig, sondern an einer Stelle falsch — dort stand „Kein Upload-Verzeichnis, keine erzeugten Dateien“. Die Datei ist neu geschrieben: drei Dinge statt zwei,tarfür beide Betriebswege, derchown-Fallstrick nach einer Rücksicherung alsroot, ein Sicherungsskript für den systemd-Timer, das Datenbank und Anhänge im selben Lauf mitnimmt, und die Gegenprobe über die Zahlen.- Sicherung und Rücksicherung wirklich gefahren, nicht beschrieben: Abzug gezogen, Datenbank
verworfen und neu angelegt, Verzeichnis gelöscht, erst nur die Datenbank zurückgespielt — und da
meldete der Abruf genau den dokumentierten Satz „Die Datei zu … fehlt im Dateisystem“ —, dann die
Anhänge dazu, und die ausgelieferten Bytes gegen die gespeicherte
pruefsummegeprüft. Beide stimmten. tests/datenbankregeln.test.tsmusste nachziehen. Er zählt die handgeschriebenen Datenbankobjekte namentlich auf; die zwei neuen CHECK-Bedingungen und die beiden zusammengesetzten Fremdschlüssel standen nicht drin, und der Test wurde dadurch rot — genau wie gewollt./daten/ist jetzt in.gitignore. Ohne das läge das erste hochgeladene Foto im nächsten Commit.