From 94f6d7424907f63ce2b07bfc56fa0be6cdaa8fed Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 09:38:32 +0000 Subject: [PATCH] =?UTF-8?q?chore:=20echte=20Appwrite-Projekt-ID=20=C3=BCbe?= =?UTF-8?q?rnehmen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- deploy/README.md | 5 +++-- deploy/compose-snippet.yml | 3 ++- deploy/traefik/aircard.yml | 6 +++--- docs/appwrite-console.md | 4 +++- web/scripts/appwrite-setup.mjs | 26 +++++++++++++++++++------- web/scripts/appwrite-setup.test.mjs | 27 ++++++++++++++++++--------- web/src/account/appwrite.ts | 2 +- 7 files changed, 49 insertions(+), 24 deletions(-) diff --git a/deploy/README.md b/deploy/README.md index 3eb75d7..1ac9126 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -9,7 +9,7 @@ Die Seite wird auf dem Webklar-Server aus dem Gitea-Repo `knso/AirCardIOS` gebau | `deploy/deploy.sh` | `aircard-ios.org/deploy.sh` (ausführbar) | klont/aktualisiert das Repo, baut `web/` und kopiert `dist/` nach `out/` | | `deploy/webhook-server.js` | `aircard-ios.org/webhook-server.js` | nimmt den Gitea-Webhook an, startet `deploy.sh` nacheinander, pollt alle 15 Minuten | | `deploy/nginx/aircard-ios.org.conf` | `aircard-ios.org/nginx/aircard-ios.org.conf` | nginx-Konfiguration der Seite | -| `deploy/traefik/aircard.yml` | `traefik/dynamic/aircard.yml` | Route `/v1` mit Header `X-Appwrite-Project: aircard` an Appwrite | +| `deploy/traefik/aircard.yml` | `traefik/dynamic/aircard.yml` | Route `/v1` mit Header `X-Appwrite-Project: 6ac2130a00289343d6d5` an Appwrite | | `deploy/compose-snippet.yml` | `docker-compose.yml` (nach dem eship-Block) | Dienste `aircard-build` und `aircard` | Weitere Ordner unter `aircard-ios.org/` (nicht im Repo): `build/` (Klon des Repos, Arbeitsordner des Build-Containers), `out/` (gebaute Seite, von nginx ausgeliefert), `dev/` (Arbeitskopie für die Entwicklung, nicht gemountet). @@ -76,7 +76,8 @@ Repo `knso/AirCardIOS` → Einstellungen → Webhooks → Gitea: - **Trigger**: `webhook` und manuelle Aufrufe bauen immer. `initial` (Container-Start) und `poll` (alle 15 Minuten) bauen nur, wenn es einen neuen Commit gibt oder `out/index.html` fehlt. - **Gemerkter Commit**: `build/.git/aircard-last-deployed-commit` enthält den zuletzt **versuchten** Commit, geschrieben vor dem Build. Ein kaputter Build wird deshalb nicht alle 15 Minuten wiederholt; die alte Seite bleibt online, bis ein neuer Commit oder ein Webhook-/manueller Lauf kommt. - **Fehler**: Ein fehlgeschlagener Lauf wird mit `[webhook] … deployment failed` protokolliert und beendet den Server nicht. Fehlerzeilen des Skripts beginnen mit `ERROR:`. -- **Build-Variablen**: `VITE_APPWRITE_ENDPOINT` und `VITE_APPWRITE_PROJECT_ID` kommen aus der Container-Umgebung (compose); Vite liest sie direkt. +- **Build-Variablen**: `VITE_APPWRITE_ENDPOINT` und `VITE_APPWRITE_PROJECT_ID` kommen aus der Container-Umgebung (compose); `AIRCARD_APPWRITE_PROJECT_ID` wird aus `/home/webklar/apps/.env` gelesen. Vite liest sie direkt. +- **.env-Variablen**: `AIRCARD_APPWRITE_PROJECT_ID` (Projekt-ID `6ac2130a00289343d6d5`) und `AIRCARD_APPWRITE_API_KEY` (für das Setup-Skript) müssen in `/home/webklar/apps/.env` eingetragen sein. ## Änderungen später diff --git a/deploy/compose-snippet.yml b/deploy/compose-snippet.yml index 635efb9..32e264d 100644 --- a/deploy/compose-snippet.yml +++ b/deploy/compose-snippet.yml @@ -6,6 +6,7 @@ # Starten NUR mit: docker compose up -d aircard-build aircard # (ein nacktes `up -d` würde andere Dienste wieder anlegen) # Die Appwrite-Route liegt als Datei in traefik/dynamic/aircard.yml. + # AIRCARD_APPWRITE_PROJECT_ID kommt aus /home/webklar/apps/.env. # ============================================== aircard-build: image: node:22-alpine @@ -28,7 +29,7 @@ - POLL_INTERVAL_MINUTES=15 - NODE_OPTIONS=--max-old-space-size=512 - VITE_APPWRITE_ENDPOINT=https://aircard-ios.org/v1 - - VITE_APPWRITE_PROJECT_ID=aircard + - VITE_APPWRITE_PROJECT_ID=${AIRCARD_APPWRITE_PROJECT_ID} volumes: - ./aircard-ios.org/build:/app - ./aircard-ios.org/out:/out diff --git a/deploy/traefik/aircard.yml b/deploy/traefik/aircard.yml index d1bc0fb..a8f17a0 100644 --- a/deploy/traefik/aircard.yml +++ b/deploy/traefik/aircard.yml @@ -1,7 +1,7 @@ -# Appwrite-Route für aircard-ios.org (Projekt "aircard" auf der bestehenden Appwrite-Instanz). +# Appwrite-Route für aircard-ios.org (Projekt mit ID `6ac2130a00289343d6d5` auf der bestehenden Appwrite-Instanz). # # Das Frontend ruft https://aircard-ios.org/v1/... auf und sendet den Header -# X-Appwrite-Project: aircard. Nur Anfragen MIT diesem Header gehen an Appwrite; alles andere +# X-Appwrite-Project: 6ac2130a00289343d6d5. Nur Anfragen MIT diesem Header gehen an Appwrite; alles andere # unter /v1 (z. B. ein oder ein Aufruf ohne Header) landet beim nginx der Seite und # bekommt dort 404. # @@ -13,7 +13,7 @@ http: routers: aircard-api: - rule: "Host(`aircard-ios.org`) && PathPrefix(`/v1`) && Header(`X-Appwrite-Project`, `aircard`)" + rule: "Host(`aircard-ios.org`) && PathPrefix(`/v1`) && Header(`X-Appwrite-Project`, `6ac2130a00289343d6d5`)" entryPoints: - websecure service: appwrite@docker diff --git a/docs/appwrite-console.md b/docs/appwrite-console.md index 2b3a7d7..df75d2d 100644 --- a/docs/appwrite-console.md +++ b/docs/appwrite-console.md @@ -1,9 +1,11 @@ -# Appwrite-Console: Projekt „AirCard“ einrichten +# Appwrite-Console: Projekt „AirCard” einrichten Klickliste für Kenso. Dauer: etwa 5 Minuten. Console: Alles passiert in der bestehenden Organisation. Das Schema (Datenbank, Tabelle, Bucket) legt danach das Skript `web/scripts/appwrite-setup.mjs` an – dafür musst du nichts anklicken. +**Hinweis:** Das Projekt wird mit der automatisch generierten ID `6ac2130a00289343d6d5` angelegt und dort in `/home/webklar/apps/.env` unter `AIRCARD_APPWRITE_PROJECT_ID` gespeichert. Das Skript liest diese ID aus der Umgebungsvariablen; der Wert muss beim Aufruf verfügbar sein. + ## 1. Projekt anlegen 1. In der Console anmelden und die bestehende Organisation öffnen. diff --git a/web/scripts/appwrite-setup.mjs b/web/scripts/appwrite-setup.mjs index a891740..060e668 100644 --- a/web/scripts/appwrite-setup.mjs +++ b/web/scripts/appwrite-setup.mjs @@ -29,7 +29,6 @@ import { pathToFileURL } from 'node:url'; export const DEFAULT_ENDPOINT = 'https://appwrite.webklar.com/v1'; -const PROJECT = 'aircard'; // --------------------------------------------------------------------------- // Soll-Zustand @@ -166,11 +165,11 @@ function createApi({ endpoint, key, project }) { } /** Klare deutsche Meldung für eine unerwartete Antwort. */ -function describeFailure(status, body, what) { +function describeFailure(status, body, what, project) { const detail = [body?.type, body?.message].filter((part) => typeof part === 'string' && part).join(': '); const tail = detail ? ` Server: ${detail}` : ''; if (status === 404 && body?.type === 'project_not_found') { - return `Das Projekt „${PROJECT}“ existiert auf dem Server nicht. Bitte zuerst docs/appwrite-console.md abarbeiten (Projekt mit der ID „${PROJECT}“ anlegen).`; + return `Das Projekt „${project}” existiert auf dem Server nicht. Bitte zuerst docs/appwrite-console.md abarbeiten und die AIRCARD_APPWRITE_PROJECT_ID in /home/webklar/apps/.env prüfen.`; } if (status === 401 || status === 403) { return `${what}: Zugriff verweigert (HTTP ${status}). API-Key und Scopes prüfen (databases, tables, columns, buckets – jeweils read/write).${tail}`; @@ -184,13 +183,13 @@ async function getObject(ctx, path, what) { const res = await ctx.api('GET', path); if (res.status === 200) return res.body; if (isMissing(res.status, res.body)) return null; - throw new SetupError(describeFailure(res.status, res.body, what)); + throw new SetupError(describeFailure(res.status, res.body, what, ctx.project)); } async function createObject(ctx, path, body, what) { const res = await ctx.api('POST', path, body); if (res.status >= 200 && res.status < 300) return res.body; - throw new SetupError(describeFailure(res.status, res.body, what)); + throw new SetupError(describeFailure(res.status, res.body, what, ctx.project)); } // --------------------------------------------------------------------------- @@ -359,18 +358,22 @@ export async function ensureBucket(ctx) { export async function main({ endpoint = DEFAULT_ENDPOINT, key, - project = PROJECT, + project, check = false, log = console.log, pollMs = 1000, timeoutMs = 30_000, } = {}) { + if (!project) { + throw new SetupError('project-Parameter erforderlich'); + } const stats = { missing: 0, deviations: 0 }; const ctx = { api: createApi({ endpoint, key, project }), check, pollMs, timeoutMs, + project, ok: (label) => log(`OK: ${label}`), created: (label) => log(`ANGELEGT: ${label}`), missing: (label) => { @@ -426,7 +429,15 @@ async function cli() { const key = process.env.AIRCARD_APPWRITE_API_KEY?.trim(); if (!key) { console.error('Fehler: AIRCARD_APPWRITE_API_KEY fehlt.'); - console.error('Den API-Key „aircard-provision“ als Zeile AIRCARD_APPWRITE_API_KEY=… in /home/webklar/apps/.env eintragen (siehe docs/appwrite-console.md)'); + console.error('Den API-Key „aircard-provision” als Zeile AIRCARD_APPWRITE_API_KEY=… in /home/webklar/apps/.env eintragen (siehe docs/appwrite-console.md)'); + console.error('und das Skript mit --env-file=/home/webklar/apps/.env starten.'); + process.exitCode = 2; + return; + } + const project = process.env.AIRCARD_APPWRITE_PROJECT_ID?.trim(); + if (!project) { + console.error('Fehler: AIRCARD_APPWRITE_PROJECT_ID fehlt.'); + console.error('Die Projekt-ID als Zeile AIRCARD_APPWRITE_PROJECT_ID=6ac2130a00289343d6d5 in /home/webklar/apps/.env eintragen.'); console.error('und das Skript mit --env-file=/home/webklar/apps/.env starten.'); process.exitCode = 2; return; @@ -434,6 +445,7 @@ async function cli() { process.exitCode = await main({ endpoint: process.env.AIRCARD_APPWRITE_ENDPOINT || DEFAULT_ENDPOINT, key, + project, check: args.includes('--check'), }); } diff --git a/web/scripts/appwrite-setup.test.mjs b/web/scripts/appwrite-setup.test.mjs index 055c9aa..ada6379 100644 --- a/web/scripts/appwrite-setup.test.mjs +++ b/web/scripts/appwrite-setup.test.mjs @@ -25,7 +25,7 @@ const KEY = 'test-key-0123456789abcdef-geheim'; // Fake-Appwrite (1.8-ähnliche REST-Formen, alles im Speicher) // --------------------------------------------------------------------------- -function createFakeAppwrite({ project = 'aircard', key = KEY, stuckColumns = false, inject = null } = {}) { +function createFakeAppwrite({ project = '6ac2130a00289343d6d5', key = KEY, stuckColumns = false, inject = null } = {}) { const state = { databases: new Map(), tables: new Map(), // "db/table" @@ -158,7 +158,7 @@ async function run(fake, opts = {}) { const code = await main({ endpoint: fake.endpoint, key: KEY, - project: 'aircard', + project: '6ac2130a00289343d6d5', check: false, log: (line) => lines.push(line), pollMs: 1, @@ -385,7 +385,7 @@ describe('main gegen Fake-Server', () => { // Jede Anfrage trägt die drei Header for (const r of fake.state.requests) { assert.equal(r.headers['content-type'], 'application/json'); - assert.equal(r.headers['x-appwrite-project'], 'aircard'); + assert.equal(r.headers['x-appwrite-project'], '6ac2130a00289343d6d5'); assert.equal(r.headers['x-appwrite-key'], KEY); } // Der Key taucht nie in der Ausgabe auf @@ -445,15 +445,15 @@ describe('main gegen Fake-Server', () => { test('404 project_not_found bricht klar ab und meldet nichts als fehlend', async () => { await withFake({ project: 'anderes-projekt' }, async (fake) => { for (const check of [true, false]) { - const { code, lines } = await run(fake, { check }); + const { code, lines } = await run(fake, { check, project: '6ac2130a00289343d6d5' }); assert.equal(code, 2); assert.equal(startsWith(lines, 'FEHLT').length, 0); assert.equal(startsWith(lines, 'ANGELEGT').length, 0); const abort = startsWith(lines, 'ABBRUCH'); assert.equal(abort.length, 1); assert.match(abort[0], /Projekt/); - assert.match(abort[0], /aircard/); - assert.match(abort[0], /docs\/appwrite-console\.md/); + assert.match(abort[0], /6ac2130a00289343d6d5/); + assert.match(abort[0], /AIRCARD_APPWRITE_PROJECT_ID/); assert.ok(!lines.join('\n').includes(KEY)); } assert.equal(fake.posts().length, 0); @@ -538,15 +538,23 @@ describe('main gegen Fake-Server', () => { describe('CLI', () => { test('ohne AIRCARD_APPWRITE_API_KEY: deutsche Fehlermeldung, Exit 2', async () => { - const { code, stdout, stderr } = await runCli([], {}); + const { code, stdout, stderr } = await runCli([], { AIRCARD_APPWRITE_PROJECT_ID: '6ac2130a00289343d6d5' }); assert.equal(code, 2); assert.equal(stdout, ''); assert.match(stderr, /AIRCARD_APPWRITE_API_KEY/); assert.match(stderr, /fehlt/); }); + test('ohne AIRCARD_APPWRITE_PROJECT_ID: deutsche Fehlermeldung, Exit 2', async () => { + const { code, stdout, stderr } = await runCli([], { AIRCARD_APPWRITE_API_KEY: KEY }); + assert.equal(code, 2); + assert.equal(stdout, ''); + assert.match(stderr, /AIRCARD_APPWRITE_PROJECT_ID/); + assert.match(stderr, /fehlt/); + }); + test('unbekannte Option: Exit 2', async () => { - const { code, stderr } = await runCli(['--foo'], { AIRCARD_APPWRITE_API_KEY: KEY }); + const { code, stderr } = await runCli(['--foo'], { AIRCARD_APPWRITE_API_KEY: KEY, AIRCARD_APPWRITE_PROJECT_ID: '6ac2130a00289343d6d5' }); assert.equal(code, 2); assert.match(stderr, /Unbekannte Option/); assert.ok(!stderr.includes(KEY)); @@ -556,6 +564,7 @@ describe('CLI', () => { await withFake({}, async (fake) => { const { code, stdout, stderr } = await runCli(['--check'], { AIRCARD_APPWRITE_API_KEY: KEY, + AIRCARD_APPWRITE_PROJECT_ID: '6ac2130a00289343d6d5', AIRCARD_APPWRITE_ENDPOINT: fake.endpoint, }); assert.equal(code, 1); @@ -568,7 +577,7 @@ describe('CLI', () => { test('Anlegen und danach --check über die Kommandozeile: Exit 0', async () => { await withFake({}, async (fake) => { - const env = { AIRCARD_APPWRITE_API_KEY: KEY, AIRCARD_APPWRITE_ENDPOINT: fake.endpoint }; + const env = { AIRCARD_APPWRITE_API_KEY: KEY, AIRCARD_APPWRITE_PROJECT_ID: '6ac2130a00289343d6d5', AIRCARD_APPWRITE_ENDPOINT: fake.endpoint }; const apply = await runCli([], env); assert.equal(apply.code, 0, apply.stdout + apply.stderr); const check = await runCli(['--check'], env); diff --git a/web/src/account/appwrite.ts b/web/src/account/appwrite.ts index 7edc81a..bb35a29 100644 --- a/web/src/account/appwrite.ts +++ b/web/src/account/appwrite.ts @@ -3,7 +3,7 @@ import { Account, Client, Storage, TablesDB } from 'appwrite'; export const APPWRITE_ENDPOINT: string = import.meta.env.VITE_APPWRITE_ENDPOINT || `${location.origin}/v1`; -export const APPWRITE_PROJECT: string = import.meta.env.VITE_APPWRITE_PROJECT_ID || 'aircard'; +export const APPWRITE_PROJECT: string = import.meta.env.VITE_APPWRITE_PROJECT_ID || '6ac2130a00289343d6d5'; /** Datenbank, Tabelle und Bucket des Projekts (siehe web/scripts/appwrite-setup.mjs). */ export const IDS = { database: 'aircard', table: 'cards', bucket: 'card-images' } as const;