Phase C: die letzten sechs Typen — PDF, Bild, Feedback und Datei-Upload
Damit sind alle 15 QR-Typen gebaut. PDF und Bild brauchen eine hochgeladene Datei. Die liegt in der Datenbank, nicht auf der Platte: damit bleibt es bei einem Volumen, das gesichert werden muss — ein zweites wird beim Sichern zu leicht vergessen. Geprueft wird der Dateiinhalt, nicht die Endung; eine Textdatei namens harmlos.pdf kommt nicht durch. SVG ist bewusst nicht erlaubt, weil es Skripte enthalten darf und von derselben Adresse wie die Anwendung ausgeliefert wuerde. Ausgeliefert wird mit nosniff und einer CSP-Sandbox, damit auch ein PDF mit Skripten nichts anrichtet. Dateien haengen am Code: wird er geloescht, geht sie mit. Eine ausgetauschte oder nie gespeicherte Datei wird nach 24 Stunden weggeraeumt. Eine fremde Datei laesst sich nicht an den eigenen Code haengen — sonst koennte man sie einem anderen Code entziehen. Feedback sammelt Note und Kommentar, ohne Name, IP oder Geraetekennung. Die Auswertung mit Durchschnitt, Verteilung und Kommentaren ist aus der Codeliste erreichbar — ein Feedback-Code ohne Auswertung waere nur die halbe Sache.
This commit is contained in:
@@ -180,8 +180,59 @@ model QrCode {
|
||||
createdById String?
|
||||
createdBy User? @relation("QrCodeCreator", fields: [createdById], references: [id], onDelete: SetNull)
|
||||
|
||||
/// Dateien, die zu diesem Code gehören — beim Löschen gehen sie mit.
|
||||
uploads Upload[]
|
||||
/// Rückmeldungen, die über die Landingpage eingegangen sind.
|
||||
feedback Feedback[]
|
||||
|
||||
@@unique([domainId, slug])
|
||||
@@index([createdAt])
|
||||
@@index([groupId])
|
||||
@@index([domainId])
|
||||
}
|
||||
|
||||
/// Eine hochgeladene Datei — das PDF hinter einem PDF-Code, das Bild hinter
|
||||
/// einem Bild-Code.
|
||||
///
|
||||
/// Der Inhalt liegt in der Datenbank, nicht auf der Platte. Damit gibt es
|
||||
/// weiterhin genau ein Volumen zu sichern: Wer die Datenbank sichert, hat
|
||||
/// alles. Bei Dateigrößen im einstelligen Megabyte-Bereich und einer
|
||||
/// Installation dieser Größenordnung ist das der bessere Tausch als ein
|
||||
/// zweites Volumen, das beim Sichern vergessen werden kann.
|
||||
model Upload {
|
||||
id String @id @default(cuid())
|
||||
/// Ursprünglicher Dateiname, für den Download.
|
||||
filename String
|
||||
mimeType String
|
||||
/// Größe in Bytes — als Angabe für die Oberfläche, ohne die Datei zu laden.
|
||||
size Int
|
||||
data Bytes
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
/// Der Code, zu dem die Datei gehört. Leer, solange sie nur hochgeladen,
|
||||
/// der Code aber noch nicht gespeichert wurde; solche Reste räumt
|
||||
/// `src/lib/uploads.ts` nach einer Weile weg.
|
||||
qrCodeId String?
|
||||
qrCode QrCode? @relation(fields: [qrCodeId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([qrCodeId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
/// Eine Rückmeldung, die jemand über die Landingpage eines Feedback-Codes
|
||||
/// abgegeben hat.
|
||||
///
|
||||
/// Bewusst ohne jeden Bezug zur Person: keine IP-Adresse, kein Kennzeichen
|
||||
/// des Geräts. Wer einen Zettel an der Wand scannt und eine Note vergibt,
|
||||
/// soll nicht wiedererkennbar sein.
|
||||
model Feedback {
|
||||
id String @id @default(cuid())
|
||||
qrCodeId String
|
||||
qrCode QrCode @relation(fields: [qrCodeId], references: [id], onDelete: Cascade)
|
||||
/// 1 bis 5.
|
||||
rating Int
|
||||
comment String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([qrCodeId, createdAt])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user