diff --git a/README.md b/README.md index c499cff..8305512 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,25 @@ # AirCard-IOS -Inoffizielles Hobbyprojekt, mit dem man eigene Kartenmotive für Apple Wallet unter https://aircard-ios.org gestalten kann. Bilder hochladen, Vorlage und Netzwerk-Logo wählen, herunterladen – oder mit Konto speichern. Keine Verbindung zu Apple, Visa, Mastercard, UnionPay, American Express, JCB oder Discover. +Inoffizielles Hobbyprojekt, mit dem man eigene Kartenmotive für Apple Wallet unter https://aircard-ios.org gestalten kann. Bild hochladen, Vorlage und Community-Logo wählen, als PNG herunterladen – oder mit Konto speichern und in der Community-Galerie veröffentlichen. Keine Verbindung zu Apple, Visa, Mastercard, UnionPay, American Express, JCB oder Discover; Logos und Motive stammen von Nutzern, alle Marken gehören ihren Inhabern. + +## Funktionen + +- **Editor** (`/gestalten`): Bild hochladen, Vorlage, Ausschnitt, Details (Hell/Dunkel) und das Logo oben links (AirCard-IOS) wählen, als PNG herunterladen oder mit Konto speichern („Meine Karten“, `/karten`). +- **Community-Logos** unten rechts auf der Karte: Angemeldete laden Logos hoch (PNG, JPG, WebP oder SVG; der Browser schneidet sie zu, verkleinert sie auf höchstens 720 × 250 px und lädt ein PNG hoch), alle können sie wählen. Eingebaute Marken-Logos gibt es nicht. Je Konto und Logo gibt es ein Herz (nur angemeldet); die Herzen bestimmen die Sortierung „Beliebt“. Logos lassen sich melden (ab 3 Meldern aus verschiedenen Netzen werden sie ausgeblendet) und vom Hochlader löschen; Admins (Label `admin`) sehen zusätzlich „Gemeldet“ und „Ausgeblendet“. +- **Community-Galerie** (Startseite `/` und `/community/`): Karten veröffentlichen, ansehen, melden und zurückziehen. +- **Deutsch und Englisch**: Die Sprache folgt der Browsersprache; der Sprachknopf in der Kopfzeile schaltet ohne Neuladen um und merkt sich die Wahl. Daneben steht ein GitHub-Link zum Original-Projekt [AirCard-iOS](https://github.com/Mak5er/AirCard-iOS). ## Aufbau **`web/`** – Vite + TypeScript ohne Framework. Frontend-Module in `src/`: - `card/` – Geometrie, Zuschnitt, Canvas-Rendering (keine DOM-Abhängigkeiten, einzeln testbar) - `editor/` – UI (Vorlagen, Vorschau, Download, Speichern) + - `logos/` – Community-Logos: Lesen aus Appwrite, Auswahl mit Herzen und Infozeile (Melden, Löschen, Admin), Hochladen mit Aufbereitung im Browser (Zuschnitt, Verkleinern, PNG) + - `community/` – Galerie: Lesen, Veröffentlichen, Melden, Aufrufe des API-Dienstes - `account/` – Appwrite-Client, Auth, Kartenverwaltung - - `pages/` – Routen (`/`, `/karten`, `/passwort-neu` via History-API) + - `i18n/` – Deutsch und Englisch: `t('Deutscher Text')` und `msg('…')`, Wörterbuch Deutsch → Englisch in `en.ts` (ein Test verlangt für jeden Text einen Eintrag) + - `pages/` – Routen (`/`, `/gestalten`, `/karten`, `/community/`, `/passwort-neu` via History-API) + - `shell/` – Kopfzeile (GitHub-Link, Sprachknopf, Hell/Dunkel), Fußzeile, Spenden-Banner **`deploy/`** – Webhook-Router (Port 8087), Build-Skript (`deploy.sh`), nginx-Config, Traefik-Route für Appwrite. @@ -36,8 +47,9 @@ Im Frontend (`web/`): ```sh cd web -npx vitest run # Unit-Tests (card/*, editor/*, account/*, pages/*) -npx tsc --noEmit # TypeScript-Prüfung +npx vitest run # Unit-Tests (alle Module unter src/) +npx tsc --noEmit # TypeScript-Prüfung, auch der Tests +npm run build # Produktions-Build; die Prüfung davor nutzt tsconfig.build.json (ohne Tests) ``` Im Repo-Wurzelverzeichnis: @@ -45,12 +57,15 @@ Im Repo-Wurzelverzeichnis: ```sh node --test deploy/webhook-server.test.js # Webhook-Server (deploy.sh wird dort durch einen Stub ersetzt) node --test deploy/deploy.test.js # deploy.sh (echtes Skript, lokaler git-Remote, npm-Stub) +node --test "deploy/api/*.test.js" # API-Dienst (Glob in Anführungszeichen) node --test web/scripts/appwrite-setup.test.mjs # Appwrite-Schema-Skript ``` ## Deploy -**Automatisch:** Push auf `main` → Gitea-Webhook → Container `webklar-aircard-build` führt `npm ci` und `vite build` aus → Ergebnis nach `out/` → nginx `webklar-aircard` liefert. +**Automatisch:** Push auf `main` → Gitea-Webhook → Container `webklar-aircard-build` führt `npm ci` und `npm run build` (Typprüfung ohne Tests, dann `vite build`) aus → Ergebnis nach `out/` → nginx `webklar-aircard` liefert. + +**Reihenfolge, wenn eine Änderung auch Schema und API braucht** (wie die Community-Logos): erst das Schema anlegen, dann die API-Dateien ersetzen und neu starten, zuletzt `main` pushen. Die Schritte stehen in [deploy/README.md](deploy/README.md), Abschnitt „Reihenfolge bei Änderungen an Schema, API und Seite“. **Manuell:** ```sh @@ -62,7 +77,7 @@ Starten nur mit: docker compose up -d aircard-build aircard aircard-api ``` -`aircard-api` ist der kleine API-Dienst der Community-Galerie (`deploy/api/`, Route `aircard-ios.org/api`, siehe `deploy/api/README.md`). +`aircard-api` ist der kleine API-Dienst der Community-Galerie und der Community-Logos (`deploy/api/`, Route `aircard-ios.org/api`, siehe `deploy/api/README.md`). Die Appwrite-API wird über `/home/webklar/apps/traefik/dynamic/aircard.yml` geroutet (Vorlage: `deploy/traefik/aircard.yml`): Requests auf `aircard-ios.org/v1` mit Header `X-Appwrite-Project: 6ac2130a00289343d6d5` gehen an den Appwrite-Container. Ohne diesen Header antwortet der nginx der Seite mit 404. @@ -70,7 +85,7 @@ Die Appwrite-API wird über `/home/webklar/apps/traefik/dynamic/aircard.yml` ger Projekt „AirCard“ auf `appwrite.webklar.com`: - Projekt-ID (öffentlich, von Appwrite generiert): `6ac2130a00289343d6d5`. Sie steht in `deploy/traefik/aircard.yml`, in `/home/webklar/apps/.env` (`AIRCARD_APPWRITE_PROJECT_ID`) und als Standard in `web/src/account/appwrite.ts`. -- Datenbank: `aircard`, Tabelle: `cards`, Bucket: `card-images` (das Skript unten legt sie an und prüft sie) +- Datenbank: `aircard`; Tabellen: `cards` (private Karten), `gallery` und `reports` (Community-Galerie, Meldungen), `logos`, `logo_likes` und `logo_reports` (Community-Logos, Herzen, Meldungen); Buckets: `card-images`, `gallery-images` und `logo-images` (das Skript unten legt sie an und prüft sie) - Console-Einstellungen laut `docs/appwrite-console.md` (Soll-Stand, nicht maschinell geprüft; `--check` prüft nur das Schema): Auth mit E-Mail + Passwort, Sitzungsdauer 30 Tage, max. 5 Sitzungen pro Nutzer, Nutzerlimit 2000, Web-Plattformen `aircard-ios.org` und `localhost`. Schritte 3 und 4 dort (Auth-Härtung, Dienste abschalten) sind optional und am Live-Projekt unbestätigt. **Schema prüfen:** diff --git a/deploy/README.md b/deploy/README.md index ff7c202..437dc08 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -60,10 +60,24 @@ Kleiner Node-Dienst ohne Abhängigkeiten (`deploy/api/`), Container `webklar-air - **Route**: Traefik-Label am Dienst, Router `aircard-gallery-api` (``Host(`aircard-ios.org`) && PathPrefix(`/api`)``, Priorität 200, Port 8088). Der Name ist absichtlich nicht `aircard-api`: so heißt schon die Appwrite-Route (`/v1`) in `traefik/dynamic/aircard.yml`. - **Prüfen** nach dem Start: `curl -s https://aircard-ios.org/api/gesundheit` liefert `{"ok":true}`; `docker logs webklar-aircard-api` zeigt `listening on :8088`. Läuft der Dienst nicht, fehlt auch sein Traefik-Router: nginx antwortet dann auf `/api/*` mit `503` und `{"error":"Der Dienst ist gerade nicht erreichbar."}` (Content-Type `application/json`), nie mit der App. - **Ändern**: Dateien mit `cp` nach `aircard-ios.org/api/` ersetzen (alle fünf: `appwrite.js`, `common.js`, `app.js`, `logos.js`, `server.js`; `app.js` lädt `common.js` und `logos.js`, ohne sie startet der Dienst nicht), dann `cd /home/webklar/apps && docker compose restart aircard-api`. Nach einer Änderung der `.env` (Key, Salz) reicht `restart` nicht: `docker compose up -d aircard-api`. -- **Logos brauchen das Schema**: Die Logo-Endpunkte schreiben in die Tabellen `logos`, `logo_likes`, `logo_reports` und den Bucket `logo-images`. Das Schema-Skript `web/scripts/appwrite-setup.mjs` legt sie an (zuerst mit `--check` ansehen, was fehlt, siehe [../README.md](../README.md)); das gehört vor den Neustart des API-Dienstes mit den neuen Dateien. Ohne die Tabellen antworten die Logo-Endpunkte mit `500` „Interner Fehler.“, die Karten-Endpunkte laufen weiter. +- **Logos brauchen das Schema**: Die Logo-Endpunkte schreiben in die Tabellen `logos`, `logo_likes`, `logo_reports` und den Bucket `logo-images`. Das Schema-Skript `web/scripts/appwrite-setup.mjs` legt sie an (zuerst mit `--check` ansehen, was fehlt, siehe [../README.md](../README.md)); das gehört vor den Neustart des API-Dienstes mit den neuen Dateien. Ohne die Tabellen antworten die Logo-Endpunkte mit `500` „Interner Fehler.“, die Karten-Endpunkte laufen weiter. Die ganze Reihenfolge steht im nächsten Abschnitt. - **Log-Grenze**: Der Docker-Daemon des Servers hat keine eigenen Log-Optionen, deshalb steht im Service `logging: json-file` mit `max-size: 5m` und `max-file: 2` (höchstens 10 MB). Der Dienst selbst schreibt kein Zugriffsprotokoll und höchstens 120 Zeilen pro Minute. - **Kein Port nach außen**: der Dienst vertraut dem Header `X-Real-Ip` (setzt Traefik) für Rate-Limit und Melder-Hash, deshalb nie `ports:` ergänzen. Im Docker-Netz `webklar-network` erreichen ihn andere Container trotzdem und könnten `X-Real-Ip` setzen: akzeptiertes Risiko, siehe [api/README.md](api/README.md) (Client-IP). Vor einem AAAA-Eintrag für aircard-ios.org die Client-IP-Ermittlung prüfen. +## Reihenfolge bei Änderungen an Schema, API und Seite + +Bringt eine Änderung neue Tabellen, neue Endpunkte und eine Seite mit, die beides braucht (wie die Community-Logos), gilt immer diese Reihenfolge. Die Seite geht zuletzt live: sonst ruft sie Endpunkte auf, die es noch nicht gibt, und ein Push von `main` baut sie sofort. + +1. **Schema**: `node --env-file=/home/webklar/apps/.env web/scripts/appwrite-setup.mjs --check` zeigt, was fehlt (`FEHLT: …`). Ohne `--check` legt das Skript es an. Danach noch einmal mit `--check`: die Ausgabe muss mit `OK: Schema vollständig` enden (Exit-Code 0). Für die Logos sind das die Tabellen `logos`, `logo_likes`, `logo_reports` und der Bucket `logo-images`. +2. **API**: alle fünf Dateien (`appwrite.js`, `common.js`, `app.js`, `logos.js`, `server.js`) mit `cp` nach `aircard-ios.org/api/` kopieren, dann `cd /home/webklar/apps && docker compose restart aircard-api`. `docker logs webklar-aircard-api` zeigt `listening on :8088`. Prüfen, dass die Logo-Endpunkte da sind: ein `POST` auf `/api/logos/herz` ohne JWT muss mit `401` und `"code":"unauthorized"` antworten (der Header `Content-Type: application/json` gehört dazu, sonst kommt `415`): + + ```sh + curl -s -i -X POST -H 'Content-Type: application/json' -d '{}' https://aircard-ios.org/api/logos/herz + ``` + + Antwortet er mit `404`, läuft noch die alte API (Dateien oder Neustart prüfen). Das Schema prüft dieser Aufruf nicht, denn die Anmeldung kommt vor jedem Zugriff auf Appwrite: dafür ist Schritt 1 da. +3. **Seite**: erst jetzt `main` pushen (Gitea-Webhook, siehe unten). Der Build führt `npm ci` und `npm run build` aus: Typprüfung ohne Testdateien (`web/tsconfig.build.json`), dann `vite build`. + ## Manuell deployen ```sh diff --git a/docs/appwrite-console.md b/docs/appwrite-console.md index 51388e0..e19b459 100644 --- a/docs/appwrite-console.md +++ b/docs/appwrite-console.md @@ -2,7 +2,7 @@ Klickliste für Kenso. Dauer: etwa 5 Minuten. Console: -Alles passiert in der bestehenden Organisation. Das Schema (Datenbank, Tabellen `cards`, `gallery`, `reports` mit Spalten und Indizes, Buckets `card-images` und `gallery-images`) legt danach das Skript `web/scripts/appwrite-setup.mjs` an – dafür musst du nichts anklicken. +Alles passiert in der bestehenden Organisation. Das Schema (Datenbank, Tabellen `cards`, `gallery`, `reports`, `logos`, `logo_likes` und `logo_reports` mit Spalten und Indizes, Buckets `card-images`, `gallery-images` und `logo-images`) legt danach das Skript `web/scripts/appwrite-setup.mjs` an – dafür musst du nichts anklicken. **Hinweis:** Das Projekt wurde mit der automatisch generierten ID `6ac2130a00289343d6d5` angelegt (nicht mit einer selbst gewählten ID). Diese ID steht in `/home/webklar/apps/.env` unter `AIRCARD_APPWRITE_PROJECT_ID`; das Skript liest sie von dort, der Wert muss beim Aufruf verfügbar sein. @@ -58,7 +58,7 @@ Dieser Key gehört nur dem Setup-Skript. Die Website selbst braucht ihn nicht. 4. **Create** klicken. 5. Auf der Seite des neuen Keys den **Secret**-Wert kopieren (Auge-Symbol zum Einblenden, dann kopieren). -Die Indizes der Tabellen `gallery` und `reports` legt das Skript mit denselben Scopes an; ein eigener Index-Scope ist nicht nötig. +Die Indizes der Tabellen `gallery`, `reports`, `logos`, `logo_likes` und `logo_reports` legt das Skript mit denselben Scopes an; ein eigener Index-Scope ist nicht nötig. ## 6. Key auf dem Server eintragen @@ -77,7 +77,7 @@ Der Key gehört in die Datei `/home/webklar/apps/.env`, nirgendwo sonst. ## 7. Community -Für die Community-Galerie (Tabellen `gallery` und `reports`, Bucket `gallery-images`, API-Dienst `aircard-api`) sind drei Dinge in der Console nötig. Das Schema dazu legt dasselbe Skript an (`node --env-file=/home/webklar/apps/.env web/scripts/appwrite-setup.mjs`, vorher mit `--check` ansehen, was fehlt). +Für die Community-Galerie und die Community-Logos (Tabellen `gallery`, `reports`, `logos`, `logo_likes` und `logo_reports`, Buckets `gallery-images` und `logo-images`, API-Dienst `aircard-api`) sind drei Dinge in der Console nötig. Das Schema dazu legt dasselbe Skript an (`node --env-file=/home/webklar/apps/.env web/scripts/appwrite-setup.mjs`, vorher mit `--check` ansehen, was fehlt). ### 7.1 JWT muss an sein @@ -85,7 +85,7 @@ JWT muss **an** sein – falls in Schritt 3 ausgeschaltet, wieder einschalten. A ### 7.2 Label `admin` am eigenen Konto -Das Label `admin` macht dich zum Admin der Galerie: Die Website zeigt dir dann Ein-/Ausblenden-Knöpfe und den Galerie-Filter „Gemeldet“/„Ausgeblendet“. +Das Label `admin` macht dich zum Admin der Galerie und der Community-Logos: Die Website zeigt dir dann Ein-/Ausblenden-Knöpfe und die Filter „Gemeldet“/„Ausgeblendet“ (in der Galerie und im Logo-Bereich des Editors). 1. Linke Leiste → **Auth** → **Users** → dein Konto öffnen. 2. Bei **Labels** den Wert `admin` eintragen → **Update**. @@ -100,7 +100,7 @@ Dieser Key gehört nur dem API-Dienst `aircard-api`, nicht dem Setup-Skript und - `documents.read` und `documents.write` - `files.read` und `files.write` - Die Console beschriftet die Zeilen-Scopes mit `documents.*`; das sind dieselben wie die `rows.read`/`rows.write` der Appwrite-API (Tabellen heißen dort Zeilen). Einen Eintrag `rows.*` gibt es in der Console nicht. + Die Console beschriftet die Zeilen-Scopes mit `documents.*`; das sind dieselben wie die `rows.read`/`rows.write` der Appwrite-API (Tabellen heißen dort Zeilen). Einen Eintrag `rows.*` gibt es in der Console nicht. Dieselben vier Scopes genügen für die Logo-Endpunkte (Tabellen `logos`, `logo_likes`, `logo_reports`, Bucket `logo-images`). 4. **Create** klicken und auf der Seite des Keys den **Secret**-Wert kopieren. 5. Auf dem Server den Wert verdeckt in die `.env` schreiben. Der Befehl fragt den Wert ohne Anzeige ab (`read -rs`), schreibt bei leerer Eingabe nichts, beginnt mit einem Zeilenumbruch (so klebt die Zeile nie an einer letzten Zeile ohne Umbruch) und zeigt den Key nirgends an: @@ -112,4 +112,4 @@ Dieser Key gehört nur dem API-Dienst `aircard-api`, nicht dem Setup-Skript und ## 8. Bescheid geben -Schreib Claude „Console fertig“. Danach legt Claude Datenbank `aircard`, die Tabellen `cards`, `gallery`, `reports` (mit Spalten und Indizes) und die Buckets `card-images`, `gallery-images` mit dem Skript an und prüft sie. Für die Community-Galerie schreib zusätzlich „Label und Key sind gesetzt“. +Schreib Claude „Console fertig“. Danach legt Claude Datenbank `aircard`, die Tabellen `cards`, `gallery`, `reports`, `logos`, `logo_likes`, `logo_reports` (mit Spalten und Indizes) und die Buckets `card-images`, `gallery-images`, `logo-images` mit dem Skript an und prüft sie. Für die Community-Galerie schreib zusätzlich „Label und Key sind gesetzt“.