Ein kleines Bilder-Netzwerk ohne Passwörter. Anmeldung per Passkey, Bilder werden auf dem Gerät verkleinert und von ihren Metadaten befreit, bevor sie hochgeladen werden.
Next.js 16 · React 19 · Tailwind 4 · Supabase
Lokal: SETUP.md · Online gehen: DEPLOY.md
Siehe SETUP.md — Supabase-Projekt, Passkeys, Mailversand, Migrationen. Kurzfassung:
npm install
cp .env.local.example .env.local # Werte aus dem Supabase-Dashboard eintragen
npm run check # prüft, ob die Einrichtung vollständig ist
npm run devnpm run check -- --mail verschickt zusätzlich eine echte Anmeldemail und
zeigt, woran der Versand scheitert.
Ein Passkey kann bei Supabase nicht der einzige Faktor sein — ihn zu registrieren setzt eine bestätigte Sitzung voraus. Daraus folgt:
- Erstes Mal: Code per E-Mail → angemeldet → Passkey einrichten
- Danach: ein Klick, ohne E-Mail-Eingabe
- Neues Gerät: wieder Code, dann dort einen eigenen Passkey anlegen
Der Code-Weg ist damit kein Provisorium, sondern der dauerhafte Wiedereinstieg, wenn ein Gerät verloren geht.
Bilder werden vollständig im Browser aufbereitet, bevor sie das Gerät
verlassen (lib/bild.ts): gedreht anhand der EXIF-Ausrichtung, auf 1440 Pixel
lange Kante verkleinert, als JPEG neu kodiert.
Das Neuzeichnen auf ein Canvas erzeugt eine neue Datei, die nur noch Pixel enthält. Sämtliche Metadaten verschwinden dabei — auch die GPS-Koordinaten, die Handys standardmäßig in jedes Foto schreiben. Ein Foto, das den Wohnort verrät, wird also gar nicht erst hochgeladen.
JPEG ist dabei kein Zufall: Safari unterstützt canvas.toBlob mit WebP nicht
und fällt still auf PNG zurück, was die Datei vergrößert statt sie zu
verkleinern.
| Pfad | Zweck |
|---|---|
proxy.ts |
Sitzung auffrischen, Routen schützen (hieß vor Next.js 16 middleware.ts) |
lib/supabase/ |
Clients für Browser, Server und Proxy |
lib/bild.ts |
Verkleinern, drehen, Metadaten entfernen |
lib/feed.ts |
Feed-Abfrage, Bild-URLs, relative Zeitangaben |
lib/profile.ts |
Profil des angemeldeten Nutzers |
app/login/ |
Anmeldung per Passkey oder Code |
app/willkommen/ |
Namenswahl beim ersten Anmelden |
app/hochladen/ |
Bild auswählen, aufbereiten, veröffentlichen |
app/p/[id]/ |
Einzelner Beitrag mit Kommentaren |
app/profil/ |
Profil, eigene Bilder, Passkey-Verwaltung |
app/actions.ts |
Server Actions für Likes und Kommentare |
app/glocke.tsx |
Zähler an der Glocke, in Echtzeit |
app/benachrichtigungen/ |
Wer mag, kommentiert, folgt — zusammengefasst |
lib/benachrichtigungen-gruppieren.ts |
Reine Gruppierungslogik, mit Tests |
supabase/migrations/ |
Tabellen, Zugriffsregeln, Speicher-Bucket |
supabase/templates/ |
E-Mail-Vorlagen für das Dashboard |
scripts/check-supabase.mjs |
Prüft die Einrichtung, testet den Mailversand |
Datenbank-Migrationen laufen über den SQL Editor im Dashboard, solange kein Docker für die lokale Supabase-Umgebung installiert ist.
Alles läuft über Row Level Security in der Datenbank, nicht über Prüfungen im Anwendungscode:
- Profile, Beiträge, Likes und Kommentare dürfen alle Angemeldeten lesen
- Schreiben und Löschen nur die eigene Zeile
- Kommentare darf zusätzlich löschen, wem der Beitrag gehört
- Im Bildspeicher darf jeder nur in seinen eigenen Ordner schreiben
Bei Likes ist das Paar aus Beitrag und Nutzer der Primärschlüssel. Damit erzwingt die Datenbank, dass niemand zweimal dasselbe mag — Doppelklicks laufen ins Leere, statt Duplikate zu erzeugen.
Eine Zeile pro Ereignis, zusammengefasst erst beim Anzeigen — nach Bezug und Kalendertag. Geschrieben werden sie von Triggern in der Datenbank, nicht vom Anwendungscode: Wer ein Like setzt, darf die Benachrichtigung des Empfängers nicht selbst schreiben dürfen.
Die Zahl an der Glocke zählt Ereignisse, die Liste zeigt Gruppen. Fünf Likes auf dasselbe Bild ergeben also „5“ oben und eine Zeile unten. Anders ginge es nicht, ohne bei jedem Ereignis den Server zu fragen.
Direktnachrichten zählen mit, stehen aber nicht in der Liste — ihr Inhalt ist verschlüsselt, und in der Datenbank soll er auch nicht als Vorschau landen.
Tests: npm test (Node führt TypeScript direkt aus, kein Testwerkzeug nötig).
docs/eid-parked/ enthält ein ausgearbeitetes Konzept für Identitätsprüfung
per Personalausweis-Chip — ein Konto pro Person, ohne dass Namen oder
Ausweisnummern je den Server erreichen. Nicht umgesetzt; die README dort
erklärt Stand und Fallstricke.