feat(api): Benutzernamen (Profil-Tabelle, /api/profil, Pflicht beim Hochladen und Veröffentlichen)

- Schema: Tabelle `profiles` (Zeilen-ID = Konto-ID, `username` und `usernameKey`
  je 20 Zeichen, Unique-Index `username_key` auf `usernameKey`, rowSecurity an,
  keine Tabellenrechte) im Setup-Skript.
- Neues Modul `deploy/api/profiles.js`: `POST /api/profil` legt das Profil an
  oder ändert es (Zeilenrecht genau read("any"), Sperre je Konto). Muster,
  Sperrliste und Schlüssel (`usernameKey`) stehen in `common.js`; vergeben ->
  409 `usernameTaken`, auch wenn erst der Unique-Index bei einem Rennen
  entscheidet. Dazu die MSG-Texte `usernameInvalid`, `usernameReserved`,
  `usernameTaken`, `usernameRequired`.
- `POST /api/logos/hochladen` und `POST /api/veroeffentlichen` antworten 403
  `usernameRequired`, wenn das Konto kein Profil hat (`hasProfile`, nach der
  Anmeldung und vor allen anderen Prüfungen).
- Fake-Appwrite: Tabelle `profiles` mit Unique-Index (409), `addProfile`,
  `holdNext`. API-Dienst besteht jetzt aus sechs Dateien (READMEs).
- Client: die vier API-Texte in `API_MESSAGES` und `en.ts` (Gleichlauf mit MSG).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 06:40:44 +00:00
1 parent a65c075004
commit 19c8151c7e
15 files changed
+980 -53

No files matched your search

+14 -4
View File
@@ -3,9 +3,9 @@
* AirCard – Appwrite-Schema-Setup (idempotent)
*
* Legt im selbst gehosteten Appwrite (Projekt `aircard`) die Datenbank `aircard`,
* die Tabellen `cards`, `gallery`, `reports`, `logos`, `logo_likes` und
* `logo_reports` (TablesDB, mit Spalten und Indizes) und die Buckets
* `card-images`, `gallery-images` und `logo-images` an.
* die Tabellen `cards`, `gallery`, `reports`, `logos`, `logo_likes`,
* `logo_reports` und `profiles` (TablesDB, mit Spalten und Indizes) und die
* Buckets `card-images`, `gallery-images` und `logo-images` an.
* Reines `fetch`, keine Abhängigkeiten, Node 22+.
*
* Aufruf (vom Repo-Wurzelverzeichnis):
@@ -41,7 +41,7 @@ const str = (key, size, required) => ({ key, type: 'string', size, required });
export const DESIRED = {
database: { id: 'aircard', name: 'AirCard' },
// Reihenfolge ist Ablauf: cards, gallery, reports, logos, logo_likes, logo_reports (je Tabelle: Spalten, dann Indizes).
// Reihenfolge ist Ablauf: cards, gallery, reports, logos, logo_likes, logo_reports, profiles (je Tabelle: Spalten, dann Indizes).
tables: [
{
id: 'cards',
@@ -137,6 +137,16 @@ export const DESIRED = {
],
indexes: [{ key: 'logo', type: 'key', columns: ['logoId'] }],
},
{
// Benutzernamen: eine Zeile je Konto, Zeilen-ID = Konto-ID, öffentlich lesbar (Zeilenrecht read("any") setzt die API); geschrieben wird nur
// über die API. `usernameKey` ist der Name klein geschrieben: der Unique-Index macht ihn auch bei gleichzeitigen Anfragen nur einmal vergebbar.
id: 'profiles',
name: 'Profiles',
permissions: [],
rowSecurity: true,
columns: [str('username', 20, true), str('usernameKey', 20, true)],
indexes: [{ key: 'username_key', type: 'unique', columns: ['usernameKey'] }],
},
],
buckets: [
{