- TypeScript 98.3%
- CSS 1.4%
- Dockerfile 0.2%
- server/web package-lock.json committed; Dockerfiles and check.yml use npm ci (prevents the archiver-v8 class of failure from v0.1.103) - docs/GO-LIVE.md: pre-event checklist, linked from README - ship workflow change: keep lockfiles, commit them when deps change |
||
|---|---|---|
| .forgejo/workflows | ||
| docs | ||
| infra/proxy | ||
| server | ||
| web | ||
| .env.example | ||
| .gitignore | ||
| CHANGELOG.md | ||
| docker-compose.portainer.yml | ||
| docker-compose.yml | ||
| README.md | ||
| stack.env | ||
Juro
Selbstgehostete Awards-/Jurierungs-Plattform — angelehnt an Evalato, aber bewusst schlank: ein Stack pro Event, keine Mandanten. Einreichungen sammeln, Dateien nach S3, Jurierung mit Normalisierung, Ergebnisse exportieren. Komplett über Docker deploybar, Worker horizontal skalierbar.
Architektur
┌─────────┐ ┌──────────────┐
Browser ──┤ proxy ├── /api ─┤ api (NestJS)│── Postgres
│ (nginx) │ │ Session=Redis│── Redis (Sessions + Queues)
└────┬────┘ └──────┬────────┘── S3 (Presigned Uploads)
│ / │ enqueue
┌────┴────┐ ┌─────┴───────────────┐
│ web │ │ worker × N (BullMQ) │── S3 / SMTP
│ (React) │ │ email·attachment· │
└─────────┘ │ export │
└─────────────────────┘
- api und worker teilen sich ein Docker-Image (
juro-server), nur der Startbefehl unterscheidet sich (dist/main.jsvs.dist/worker.js). Mehr Worker = mehr Container desselben Images. - Uploads gehen per Presigned-URL direkt Browser → S3. Die API überträgt keine Dateibytes; ein Worker bestätigt anschließend den Upload und verarbeitet ihn nach (Platz für Thumbnails/Transcoding via ffmpeg).
- Single-Event: Es gibt kein Tenant-Feld.
Settingsist eine Singleton-Zeile, die Branding und Event-weite Schalter hält und im Admin-Bereich editierbar ist.
Stack
NestJS · Prisma · PostgreSQL · Redis · BullMQ · AWS SDK v3 (S3/MinIO) · React · Vite · Mantine · AG Grid · nginx · Forgejo Actions.
Funktionsumfang
Einreichung & Formulare
- Konfigurierbarer Formular-Builder (Drag & Drop, Feldtypen, Pflicht/öffentlich/identifizierend), Live-Vorschau
- Geführter Einreichungs-Wizard, Datei-Uploads per Presigned-URL, Einsendeschluss mit Auto-Schließung
- Einreicher-Portal (Magic-Link): eigene Beiträge einsehen, bearbeiten, zurückziehen; Deadline-Erinnerungs-Mails
Jurierung
- Runden mit Bewertungsarten (Punkte, Sterne, Ja/Nein, Bestanden/Nicht, Ranking, nur Kommentar)
- Gewichtete Kriterien-Rubrics, blinde Jurierung, Per-Kategorie-Juror-Scope, Interessenkonflikt-Flag
- Z-Score-Normalisierung je Juror, gewichtetes Gesamtergebnis, CSV-Export
- Insights-Dashboard: Status-Verteilung, Score-Histogramme, Juror-Strenge, höchste Uneinigkeit
Ergebnisse & Öffentlichkeit
- Veröffentlichte Ergebnisseite, öffentliche Beitrags-Detailseiten, Gewinner-Zertifikate (PDF)
- Ergebnis-Benachrichtigung per E-Mail, öffentliches Voting (E-Mail-verifiziert, optional Turnstile-Captcha / Allowlist)
- Bild-Thumbnails (sharp) für Galerie/Voting/Ergebnisse
Verwaltung & Betrieb
- Rollen: Admin, Chair (Koordinator), Juror, Einreicher; Nutzer-Einladungen, 2FA (TOTP + Recovery-Codes)
- Audit-Log, DSGVO-Werkzeuge (Auskunft-Export & Löschung), anpassbare E-Mail-Vorlagen
- Optionale Einreichgebühren via Stripe (Feature-Flag), Live-Kommentare/Presence/Spotlight (Socket.IO)
- Reiches Erscheinungsbild (Farben, Typo, Dark/Light/Auto, CSS-Variablen, eigenes CSS, Mantine-Theme-JSON)
- Mehrsprachig (DE/EN, Deutsch als Standard), Health/Readiness-Checks, optionales Error-Tracking (Sentry)
Go-Live
Vor dem ersten echten Event: docs/GO-LIVE.md durchgehen (Checkliste für
Infrastruktur, Konfiguration, Funktionsdurchlauf und Betrieb).
Links & Bereiche
Alle URLs (Teilnehmer, Jury, Öffentlich, Verwaltung) sind in docs/LINKS.md dokumentiert. Dieselbe Liste mit Kopier-Buttons und einer „Alle als Text kopieren"-Funktion findet das Admin-/Chair-Team in der App unter Admin → Links (/admin/links).
- Anleitung & typischer Ablauf: direkt in der App unter Admin → Anleitung (
/admin/help), zweisprachig (DE/EN) und in Abschnitte gegliedert – inkl. „Beispiele: 3 typische Anwendungen". Einzige Quelle: die Markdown-Abschnitte unterweb/src/content/guide/(DateienNN-slug.de.md/NN-slug.en.md). - Sicherheitsmaßnahmen: docs/SECURITY.md
Schnellstart (lokal)
cp .env.example .env # ggf. anpassen
docker compose up --build
Dann:
- App: http://localhost:8080 (Login:
admin@juro.local/admin12345) - Einreichung: http://localhost:8080/submit
- MinIO-Konsole: http://localhost:9001 (juro / juro-secret)
- MailHog: http://localhost:8025
Beim ersten Start migriert der API-Container die DB und seedet Admin + Default-Form.
Worker skalieren
docker compose up -d --scale worker=4
Jeder Worker zieht Jobs aus denselben Redis-Queues (email, attachment, export).
Failover und Retries (3×, exponentielles Backoff) sind in jobs.service.ts gesetzt.
Deploy & Releases (Forgejo)
Tags werden per Action erzeugt, nicht von Hand:
- In Forgejo Actions → release → Run workflow ausführen, Bump (patch/minor/major)
wählen. Die Action legt das nächste
vX.Y.Z-Tag an und pusht es. - Der Tag-Push triggert
build.yml: baut & pushtjuro-serverundjuro-webin die Registry, getaggt mit der Version undlatest. - Auf dem Zielhost
TAG=vX.Y.Z docker compose pull && docker compose up -d.
Vorab in Forgejo als Secrets hinterlegen: REGISTRY_USER, REGISTRY_TOKEN.
Erster Release:
build.ymlläuft nur bei einem Tag-Push. Solange noch keinvX.Y.Z-Tag existiert, gibt es keine Images in der Registry. Einmal release ausführen (oder einen Tag pushen) erzeugt sie.
Mit Portainer deployen (fertige Images ziehen)
docker-compose.portainer.yml ist eine eigenständige Variante, die die in der
Registry liegenden Images zieht statt sie zu bauen, und keine gemounteten
Host-Dateien braucht (der Proxy-Config wird inline injiziert). Damit ist sie
copy-paste-tauglich für den Portainer-Web-Editor.
- In Portainer Registries →
forgejo.thiel.toolsmit Benutzer + Token hinzufügen, dasthiel/juro-*ziehen darf (private Registry). - Stacks → Add stack → entweder das Repo als Git-Quelle angeben
(Compose-Pfad
docker-compose.portainer.yml) oder den Inhalt in den Web-Editor einfügen. - Environment bereitstellen.
apiundworkerladen ihre Konfiguration perenv_file: stack.env—stack.enventhält alle Variablen mit Defaults.- Git-Quelle: unter Environment die Environment file/Env-Datei auf
stack.envsetzen (liegt im Repo neben der Compose). - Web-Editor: die Variablen aus
stack.envin den Environment variables-Bereich übernehmen — Portainer schreibt daraus diestack.env, die die Compose dann referenziert. In beiden Fällen die mit>>> CHANGEmarkierten Werte anpassen — mindestensSESSION_SECRET,ADMIN_PASSWORD,PUBLIC_BASE_URLundS3_PUBLIC_ENDPOINT(die URL, über die der Browser das Objektspeicher erreicht — auf einem entfernten Host nichtlocalhost).TAGzeigt aufv0.1.0; für „immer neueste" auflatestsetzen.
- Git-Quelle: unter Environment die Environment file/Env-Datei auf
- Deploy. Der API-Container migriert + seedet beim ersten Start automatisch.
Produktion
SESSION_SECRETzwingend setzen,S3_*auf echtes S3 zeigen lassen (S3_FORCE_PATH_STYLE=falsebei AWS),S3_PUBLIC_ENDPOINTauf die öffentlich erreichbare S3-URL.- TLS terminiert sinnvollerweise eine Ebene über dem
proxy(Traefik/Caddy) — dannPUBLIC_BASE_URL=https://…, womit das Session-Cookie automatischsecurewird.
Datenmodell (Kern)
User (ADMIN/JUDGE/ENTRANT) · Settings (Singleton, Branding) · Category → Form
→ FormField · Entry → Attachment · Round (6 Voting-Typen) · Assignment
(Juror↔Entry, mit Conflict-of-Interest) · Score (Z-Score-normalisiert) · Tag.
Was drin ist / Roadmap
Implementiert: Infrastruktur-Stack, Auth/Sessions (inkl. TOTP-2FA), Rollen (Admin/Chair/Judge), Branding-Admin, Kategorien, Formular-Builder, öffentliche Einreichung mit S3-Direct-Upload (inkl. Bild-Downscaling, Cover, Kollaborateure), Einreicher-Portal (Magic-Link), Entries-Grid mit Tagging und Audit-Trail, Jurierungsrunden + Scoring (blind, Kategorien-Scope, Deadlines/Erinnerungen), öffentliches Voting (E-Mail-Verifikation + Turnstile), Galerie und Ergebnisseite (Zertifikate, Badges), PDF-Reports, CSV-/XLSX-Exporte, Video-Transcoding und Thumbnails im Attachment-Worker (ffmpeg/sharp), Partner, Webhooks/API-Keys, Stripe-Einreichgebühren (optional), DSGVO-Export/-Löschung, Live-Chat, i18n (DE/EN), CI mit Tag-Action.
Noch offen: CONCEPT.md ins Repo aufnehmen (Spezifikation aktuell nicht
versioniert), DTO-Validierung auf Admin-Endpunkte ausweiten, E2E-/API-Tests
gegen eine echte Datenbank.