No description
  • TypeScript 98.3%
  • CSS 1.4%
  • Dockerfile 0.2%
Find a file
thiel b84cead0f8
All checks were successful
check / verify (push) Successful in 1m11s
build / images (server, server) (push) Successful in 1m19s
build / images (web, web) (push) Successful in 40s
Reproducible builds: commit lockfiles, npm ci in Dockerfiles + CI; go-live checklist
- 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
2026-07-07 19:41:51 +00:00
.forgejo/workflows Reproducible builds: commit lockfiles, npm ci in Dockerfiles + CI; go-live checklist 2026-07-07 19:41:51 +00:00
docs Reproducible builds: commit lockfiles, npm ci in Dockerfiles + CI; go-live checklist 2026-07-07 19:41:51 +00:00
infra/proxy Live layer, entry actions & UX polish 2026-07-01 00:43:26 +00:00
server Reproducible builds: commit lockfiles, npm ci in Dockerfiles + CI; go-live checklist 2026-07-07 19:41:51 +00:00
web Reproducible builds: commit lockfiles, npm ci in Dockerfiles + CI; go-live checklist 2026-07-07 19:41:51 +00:00
.env.example Configurable SMTP + security documentation 2026-07-01 17:10:21 +00:00
.gitignore Add user & judge management with invitations and password reset 2026-06-30 22:05:27 +00:00
CHANGELOG.md Reproducible builds: commit lockfiles, npm ci in Dockerfiles + CI; go-live checklist 2026-07-07 19:41:51 +00:00
docker-compose.portainer.yml Live layer, entry actions & UX polish 2026-07-01 00:43:26 +00:00
docker-compose.yml Initial Juro scaffold: NestJS+Prisma backend, BullMQ workers, React/Mantine frontend, S3 storage, Docker stack, Forgejo CI, fully configurable appearance 2026-06-30 21:22:16 +00:00
README.md Reproducible builds: commit lockfiles, npm ci in Dockerfiles + CI; go-live checklist 2026-07-07 19:41:51 +00:00
stack.env Public visibility helpers + form-field docs/FAQ 2026-07-02 10:06:38 +00:00

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.js vs. 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. Settings ist 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).

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 unter web/src/content/guide/ (Dateien NN-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:

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:

  1. 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.
  2. Der Tag-Push triggert build.yml: baut & pusht juro-server und juro-web in die Registry, getaggt mit der Version und latest.
  3. 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.yml läuft nur bei einem Tag-Push. Solange noch kein vX.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.

  1. In Portainer Registriesforgejo.thiel.tools mit Benutzer + Token hinzufügen, das thiel/juro-* ziehen darf (private Registry).
  2. 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.
  3. Environment bereitstellen. api und worker laden ihre Konfiguration per env_file: stack.envstack.env enthält alle Variablen mit Defaults.
    • Git-Quelle: unter Environment die Environment file/Env-Datei auf stack.env setzen (liegt im Repo neben der Compose).
    • Web-Editor: die Variablen aus stack.env in den Environment variables-Bereich übernehmen — Portainer schreibt daraus die stack.env, die die Compose dann referenziert. In beiden Fällen die mit >>> CHANGE markierten Werte anpassen — mindestens SESSION_SECRET, ADMIN_PASSWORD, PUBLIC_BASE_URL und S3_PUBLIC_ENDPOINT (die URL, über die der Browser das Objekt­speicher erreicht — auf einem entfernten Host nicht localhost). TAG zeigt auf v0.1.0; für „immer neueste" auf latest setzen.
  4. Deploy. Der API-Container migriert + seedet beim ersten Start automatisch.

Produktion

  • SESSION_SECRET zwingend setzen, S3_* auf echtes S3 zeigen lassen (S3_FORCE_PATH_STYLE=false bei AWS), S3_PUBLIC_ENDPOINT auf die öffentlich erreichbare S3-URL.
  • TLS terminiert sinnvollerweise eine Ebene über dem proxy (Traefik/Caddy) — dann PUBLIC_BASE_URL=https://…, womit das Session-Cookie automatisch secure wird.

Datenmodell (Kern)

User (ADMIN/JUDGE/ENTRANT) · Settings (Singleton, Branding) · CategoryFormFormField · EntryAttachment · 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.