docs: Plan Community – Präzisierungen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
23463f7d29
commit
21e1db1858
1 file changed
+6
-5
@@ -98,7 +98,7 @@
|
||||
- Modify: `deploy/compose-snippet.yml` (Service `aircard-api`), `deploy/README.md`
|
||||
|
||||
**Interfaces:**
|
||||
- `appwrite.js`: `createAppwrite({ endpoint, project, key, fetchImpl })` → `{ getRow, listRows(table, queries), createRow, updateRow(table, id, data, permissions?), deleteRow, getFile(bucket, id), updateFilePermissions(bucket, id, permissions), deleteFile, accountFromJwt(jwt) }` (letzteres `GET /account` mit `X-Appwrite-JWT`, ohne Key). Queries als Appwrite-JSON-Strings (`{"method":"equal","attribute":"…","values":[…]}`).
|
||||
- `appwrite.js`: `createAppwrite({ endpoint, project, key, fetchImpl })` → `{ getRow, listRows(table, queries), createRow, updateRow(table, id, data, permissions?), deleteRow, getFile(bucket, id), updateFilePermissions(bucket, id, permissions), deleteFile, accountFromJwt(jwt) }` (letzteres `GET /account` mit `X-Appwrite-JWT`, ohne Key; der Client schickt das JWT im Header `X-Aircard-Jwt`). Queries als Appwrite-JSON-Strings (`{"method":"equal","attribute":"…","values":[…]}`).
|
||||
- `app.js`: `createApp({ appwrite, salt, now = Date.now, log })` → `(req, res) => Promise<void>`; reine Helfer exportiert: `visibleRowPermissions()`, `hiddenRowPermissions(owner)`, `visibleFilePermissions(owner)`, `hiddenFilePermissions(owner)`, `reporterHash(salt, ip)` (HMAC-SHA256 hex), `clientIp(req)` (`x-real-ip`, sonst erstes `x-forwarded-for`, sonst Socket), `validatePublish(body)` → `{ok, data|message}` (Längen Spec §6, Tags normalisiert: trim, klein, leer raus, max. 5, je ≤ 20), `buildSearchText({title, issuer, tags})` (klein, ≤ 300).
|
||||
- Endpunkte, Statuscodes und Texte exakt Spec §8/§11: `POST /api/veroeffentlichen` (401 ohne/ungültiges JWT, 400 Validierung, 403 Datei nicht meine/falscher Bucket, 409 „Diese Karte ist schon veröffentlicht.“, 201 `{id}`), `POST /api/zurueckziehen` (403 fremd, 404 fehlt, 200), `POST /api/melden` (400 Grund, 404 fehlt/ausgeblendet, 200 `{ok:true}` bzw. `{schonGemeldet:true}`, 429 „Zu viele Meldungen – bitte später nochmal.“), `POST /api/admin/sichtbarkeit` (403 kein Admin, 200), `GET /api/gesundheit`. Body max. 8 KB (413), nur `application/json` (415), sonst 404.
|
||||
- `server.js`: Env `AIRCARD_API_PORT` (8088), `AIRCARD_APPWRITE_ENDPOINT` (`http://webklar-appwrite/v1`), `AIRCARD_APPWRITE_PROJECT_ID`, `AIRCARD_API_KEY`, `AIRCARD_API_SALT`; fehlt Key oder Salt → Fehlermeldung ohne Wert, `exit(1)`.
|
||||
@@ -119,13 +119,14 @@
|
||||
- `api.ts`: `apiPost(path, body, { auth: boolean })` → JSON; mit `auth` holt `account.createJWT()` (Cache 10 min); Fehler als `ApiError { status, message }` mit Server-Text; Netzfehler → „Der Dienst ist gerade nicht erreichbar.“; `publishCard(input)`, `withdrawCard(id)`, `reportCard(id, reason)`, `setVisibility(id, visible)`.
|
||||
- `publish.ts`: `publishSavedCard(card: SavedCard, form: { title; issuer; tags; displayName }, deps)` → `{ id }`: Bild laden, rendern (PNG 1536 + WebP 480 via `exportCard`), zwei Uploads in `gallery-images` mit `[Permission.read(Role.any()), Permission.delete(Role.user(uid))]`, dann `publishCard`; bei Fehler beide Dateien löschen (best effort) und Fehler weiterwerfen. `normalizeTags(input: string): string[]` (wie Server).
|
||||
|
||||
- [ ] **Step 1: Tests** – Queries exakt (inkl. Suche + Cursor + Admin-Filter); `toGalleryCard` mit fehlenden Feldern; `getGalleryCard` 404 → null; `apiPost` hängt `X-Appwrite-JWT`… nein: `Authorization: Bearer <jwt>`? → Ruling: Header `X-Aircard-Jwt: <jwt>` (API liest ihn); JWT-Cache; Fehlertext aus JSON `message`; Netzfehler-Text. `publishSavedCard`: Reihenfolge Upload, Upload, API; API-Fehler → 2× `deleteFile`, Fehler kommt an; zweiter Upload scheitert → erster gelöscht.
|
||||
- [ ] **Step 1: Tests** – Queries exakt (inkl. Suche + Cursor + Admin-Filter); `toGalleryCard` mit fehlenden Feldern; `getGalleryCard` 404 → null; `apiPost` mit `auth` sendet Header `X-Aircard-Jwt: <jwt>`; JWT-Cache (zweiter Aufruf innerhalb 10 min ohne neues `createJWT`); Fehlertext aus JSON `message`; Netzfehler-Text. `publishSavedCard`: Reihenfolge Upload, Upload, API; API-Fehler → 2× `deleteFile`, Fehler kommt an; zweiter Upload scheitert → erster gelöscht.
|
||||
- [ ] **Step 2: FAIL**, **Step 3: Implementieren**, **Step 4: PASS + tsc + build**, **Step 5: Commit** `feat(community): Datenschicht für Galerie, API und Veröffentlichen`.
|
||||
|
||||
### Task 5: Startseite (Hero + Galerie)
|
||||
|
||||
**Files:**
|
||||
- Create: `web/src/pages/home-page.ts` (ersetzt Platzhalter), `web/src/community/gallery-grid.ts`, `web/src/community/community.css`, `web/public/hero-beispiel.png` (1536 × 969, eigenes markenfreies Motiv: lila/blauer Verlauf mit abstrakten Wellen + AirCard-IOS-Symbol, erzeugt mit einem Skript im Scratchpad)
|
||||
- Modify: `web/src/pages/home-page.ts` (Platzhalter aus Task 1 ersetzen)
|
||||
- Create: `web/src/community/gallery-grid.ts`, `web/src/community/community.css`, `web/public/hero-beispiel.png` (1536 × 969, eigenes markenfreies Motiv: lila/blauer Verlauf mit abstrakten Wellen + AirCard-IOS-Symbol, erzeugt mit einem Skript im Scratchpad)
|
||||
- Test: `web/src/pages/home-page.test.ts`, `web/src/community/gallery-grid.test.ts`
|
||||
|
||||
**Interfaces:**
|
||||
@@ -134,12 +135,12 @@
|
||||
- CSS: Hero `min-height: auto; max-height: 60vh`, Raster `repeat(auto-fill, minmax(260px, 1fr))`; Hero-Karte schwebt (`@media (prefers-reduced-motion: no-preference)`).
|
||||
|
||||
- [ ] **Step 1: Tests** – leere Galerie → Beispielbild + Leertext; Suche aus `?q=` landet in Queries und zeigt aktiven Begriff mit „ד; „Mehr laden“ nur bei `nextCursor`; Tag-Klick navigiert zu `/?q=<tag>#community`; Object-URLs werden beim Cleanup freigegeben.
|
||||
- [ ] **Step 2: FAIL**, **Step 3: Implementieren** (inkl. Hero-Bild), **Step 4: PASS + build**; Sichtprüfung headless Chromium (`npx vite preview`) bei 1440 × 900 und 390 × 844 mit gemockter Galerie (z. B. per `?demo=1`-freiem Weg: Test-Stub über `page.route` auf `/v1/tablesdb/…`) – Überschrift „Community-Karten“ und Oberkante der ersten Reihe im Viewport; Screenshots im Scratchpad. **Step 5: Commit** `feat(home): kompakter Hero und Community-Galerie`.
|
||||
- [ ] **Step 2: FAIL**, **Step 3: Implementieren** (inkl. Hero-Bild), **Step 4: PASS + build**; Sichtprüfung headless Chromium (`npx vite preview`) bei 1440 × 900 und 390 × 844 mit gemockter Galerie (Playwright `page.route` beantwortet `/v1/tablesdb/…/rows` mit 30 Fake-Zeilen und `/v1/storage/…/view` mit einem PNG) – Überschrift „Community-Karten“ und Oberkante der ersten Reihe im Viewport; Screenshots im Scratchpad. **Step 5: Commit** `feat(home): kompakter Hero und Community-Galerie`.
|
||||
|
||||
### Task 6: Detailseite `/community/<id>` und „Als Vorlage“
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/src/pages/community-page.ts` (ersetzt Platzhalter), `web/src/editor/editor-page.ts` (`?vorlage=<id>`)
|
||||
- Modify: `web/src/pages/community-page.ts` (Platzhalter aus Task 1 ersetzen), `web/src/editor/editor-page.ts` (`?vorlage=<id>`)
|
||||
- Create: `web/src/community/report-dialog.ts`
|
||||
- Test: `web/src/pages/community-page.test.ts`, `web/src/community/report-dialog.test.ts`, Ergänzung `web/src/editor/editor-page.test.ts`
|
||||
|
||||
|
||||
Reference in new issue
Block a user