Compare commits
6
Commits
e6fa383934
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a85bfaed4 | ||
|
|
9785eb221f | ||
|
|
0421d7d57a | ||
|
|
f29dbcf392 | ||
|
|
5a88f3dcd6 | ||
|
|
34ccbc3b9a |
No files matched your search
+3
-1
@@ -26,7 +26,9 @@ BETTER_AUTH_SECRET=<anderer-zufaelliger-wert-mindestens-32-zeichen>
|
|||||||
# sk_test_… = Testmodus (Checkout ohne Live-Freigabe moeglich)
|
# sk_test_… = Testmodus (Checkout ohne Live-Freigabe moeglich)
|
||||||
# sk_live_… = Livemodus (nur mit APP_ENV=production UND allen Freigaben)
|
# sk_live_… = Livemodus (nur mit APP_ENV=production UND allen Freigaben)
|
||||||
# Empfohlen: eingeschraenkter Schluessel (rk_…) mit Schreibrechten fuer
|
# Empfohlen: eingeschraenkter Schluessel (rk_…) mit Schreibrechten fuer
|
||||||
# Customers, Checkout Sessions, PaymentIntents, PaymentMethods, Refunds.
|
# Customers, Checkout Sessions, PaymentIntents, PaymentMethods, Refunds und
|
||||||
|
# fuer die Folgeraten (Stripe Billing) zusaetzlich Products, Subscription
|
||||||
|
# Schedules, Subscriptions, Invoices (docs/BETRIEB.md, Abschnitt 4).
|
||||||
STRIPE_SECRET_KEY=<sk_test_…>
|
STRIPE_SECRET_KEY=<sk_test_…>
|
||||||
# Signaturgeheimnis des Webhook-Endpunkts (whsec_…), siehe docs/BETRIEB.md.
|
# Signaturgeheimnis des Webhook-Endpunkts (whsec_…), siehe docs/BETRIEB.md.
|
||||||
STRIPE_WEBHOOK_SECRET=<whsec_…>
|
STRIPE_WEBHOOK_SECRET=<whsec_…>
|
||||||
|
|||||||
@@ -7,14 +7,101 @@ name: Docker-Images bauen und veroeffentlichen
|
|||||||
#
|
#
|
||||||
# Ausloeser: Push auf main (":latest" + ":sha-<kurz>"), Versions-Tags "v1.2.3"
|
# Ausloeser: Push auf main (":latest" + ":sha-<kurz>"), Versions-Tags "v1.2.3"
|
||||||
# (zusaetzlich ":1.2.3"), manuell ueber "Run workflow".
|
# (zusaetzlich ":1.2.3"), manuell ueber "Run workflow".
|
||||||
|
#
|
||||||
|
# Qualitaets-Gate: Images entstehen nur, wenn lint (inkl. audit), typecheck
|
||||||
|
# und test gruen sind. Die Pruefungen laufen auf "ci-isolated" (Jobs ohne
|
||||||
|
# Docker-Socket und ohne Clusternetz, Runner-Doku im VyCept-Repo,
|
||||||
|
# docs/BETRIEB.md 11b); nur der Image-Build braucht "docker-builder".
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
tags: ["v*"]
|
tags: ["v*"]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Ein neuer Push bricht den ueberholten Lauf ab, statt ihn zu Ende zu rechnen.
|
||||||
|
concurrency:
|
||||||
|
group: docker-build-${{ gitea.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
NEXT_TELEMETRY_DISABLED: "1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ci-isolated
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- name: Runner-Isolation pruefen
|
||||||
|
run: bash scripts/ci-runner-isolation.sh
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with: { node-version: "22" }
|
||||||
|
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/pnpm
|
||||||
|
~/.cache/pnpm-store
|
||||||
|
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
restore-keys: pnpm-
|
||||||
|
- run: export pnpm_config_store_dir="$HOME/.cache/pnpm-store" pnpm_config_cache_dir="$HOME/.cache/pnpm" && corepack enable && pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm lint
|
||||||
|
# Bekannte Luecken in Laufzeit-Abhaengigkeiten (ab "high") brechen den Lauf ab.
|
||||||
|
- run: pnpm audit --prod --audit-level high
|
||||||
|
|
||||||
|
typecheck:
|
||||||
|
runs-on: ci-isolated
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- name: Runner-Isolation pruefen
|
||||||
|
run: bash scripts/ci-runner-isolation.sh
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with: { node-version: "22" }
|
||||||
|
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/pnpm
|
||||||
|
~/.cache/pnpm-store
|
||||||
|
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
restore-keys: pnpm-
|
||||||
|
- run: export pnpm_config_store_dir="$HOME/.cache/pnpm-store" pnpm_config_cache_dir="$HOME/.cache/pnpm" && corepack enable && pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm typecheck
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ci-isolated
|
||||||
|
timeout-minutes: 15
|
||||||
|
# embedded-postgres startet nicht als root; deshalb ein Datenbank-Service
|
||||||
|
# (gleiche Version wie im Cluster), genutzt ueber TEST_DATABASE_URL
|
||||||
|
# (tests/support/global-setup.ts).
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:18.6-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: test
|
||||||
|
POSTGRES_PASSWORD: test
|
||||||
|
POSTGRES_DB: artista_test
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U test" --health-interval 5s --health-retries 10
|
||||||
|
env:
|
||||||
|
TEST_DATABASE_URL: postgresql://test:test@postgres:5432/artista_test
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- name: Runner-Isolation pruefen
|
||||||
|
run: bash scripts/ci-runner-isolation.sh
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with: { node-version: "22" }
|
||||||
|
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/pnpm
|
||||||
|
~/.cache/pnpm-store
|
||||||
|
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
restore-keys: pnpm-
|
||||||
|
- run: export pnpm_config_store_dir="$HOME/.cache/pnpm-store" pnpm_config_cache_dir="$HOME/.cache/pnpm" && corepack enable && pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm test
|
||||||
|
|
||||||
build-and-push:
|
build-and-push:
|
||||||
|
needs: [lint, typecheck, test]
|
||||||
runs-on: docker-builder
|
runs-on: docker-builder
|
||||||
env:
|
env:
|
||||||
# Oeffentlicher Registry-Host (Pull durch Kubernetes, OCI-Labels).
|
# Oeffentlicher Registry-Host (Pull durch Kubernetes, OCI-Labels).
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
# ARTISTA MODELCAMP 2027 – Buchungswebsite
|
# ARTISTA MODELCAMP 2027 – Buchungswebsite
|
||||||
|
|
||||||
Deutschsprachige Onepage mit echter Buchungsstrecke: Stripe-Zahlung (einmalig oder in bis zu drei Raten), persistente Bestell- und Gutscheinverwaltung, geschützter Adminbereich mit Zwei-Faktor-Anmeldung, Docker- und RKE2-Deployment.
|
Deutschsprachige Onepage mit echter Buchungsstrecke: Stripe-Zahlung (einmalig oder in bis zu drei Raten), persistente Bestell- und Gutscheinverwaltung, Kontaktformular für ein Erstgespräch, geschützter Adminbereich mit Zwei-Faktor-Anmeldung, Docker- und RKE2-Deployment.
|
||||||
|
|
||||||
**Stand:** technisch vollständig, im Stripe-Testmodus buchbar. Der Live-Verkauf ist serverseitig gesperrt, bis alle Inhalte und Freigaben aus [docs/OFFENE-PUNKTE.md](docs/OFFENE-PUNKTE.md) im Adminbereich bestätigt sind.
|
**Stand:** technisch vollständig, im Stripe-Testmodus buchbar. Die Seite läuft im Cluster unter https://modelcamp.artistasphotography.de (Namespace `artista`). Der Live-Verkauf ist serverseitig gesperrt, bis alle Inhalte und Freigaben aus [docs/OFFENE-PUNKTE.md](docs/OFFENE-PUNKTE.md) im Adminbereich bestätigt sind.
|
||||||
|
|
||||||
|
## Projektstand
|
||||||
|
|
||||||
|
Was gebaut, geprüft und ausgerollt ist und was noch offen ist, steht im Übersichts-Issue [#33 „Projektstand und Übersicht“](https://git.encry.de/Nick.Rosenloecher/Artista-Filmcamp/issues/33). Aufgaben, die nur der Inhaber erledigen kann (Freigaben, Rechtstexte, Secrets, Rollouts), sammelt [#32 „Inhaber-Aufgaben“](https://git.encry.de/Nick.Rosenloecher/Artista-Filmcamp/issues/32). Jedes Arbeitspaket hat ein eigenes Issue.
|
||||||
|
|
||||||
| Dokument | Inhalt |
|
| Dokument | Inhalt |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [docs/ARCHITEKTUR.md](docs/ARCHITEKTUR.md) | Entscheidungen: Stack, Preisbindung, Ratenzahlung, Kapazität, Webhooks, Design |
|
| [docs/ARCHITEKTUR.md](docs/ARCHITEKTUR.md) | Entscheidungen: Stack, Preisbindung, Ratenzahlung, Kapazität, Webhooks, Kontaktformular, Design |
|
||||||
| [docs/BETRIEB.md](docs/BETRIEB.md) | RKE2-Deployment, Secrets, Stripe-Webhooks, E-Mail, Sicherung, Storno/Erstattung |
|
| [docs/BETRIEB.md](docs/BETRIEB.md) | RKE2-Deployment, CI, Secrets, Stripe-Webhooks, E-Mail, Sicherung, Storno/Erstattung |
|
||||||
|
| [k8s/README.md](k8s/README.md) | Manifeste Schritt für Schritt anwenden, neues Image ausrollen, Fehlersuche |
|
||||||
|
| [docs/MEDIEN.md](docs/MEDIEN.md) | Dateinamen für Logo, Headerbild, Intro-Video, Porträts und Reels; Medien aufbereiten |
|
||||||
| [docs/OFFENE-PUNKTE.md](docs/OFFENE-PUNKTE.md) | Noch benötigte Inhalte und Freigaben |
|
| [docs/OFFENE-PUNKTE.md](docs/OFFENE-PUNKTE.md) | Noch benötigte Inhalte und Freigaben |
|
||||||
| [docs/TESTS.md](docs/TESTS.md) | Ausgeführte Tests und verbleibende Einschränkungen |
|
| [docs/TESTS.md](docs/TESTS.md) | Ausgeführte Tests und verbleibende Einschränkungen |
|
||||||
|
|
||||||
@@ -46,9 +52,14 @@ Testkarte `4242 4242 4242 4242`, für 3-D-Secure-Fälle `4000 0027 6000 3184`. E
|
|||||||
| `pnpm typecheck` / `pnpm lint` | Typprüfung, ESLint |
|
| `pnpm typecheck` / `pnpm lint` | Typprüfung, ESLint |
|
||||||
| `pnpm test` | Unit- und Integrationstests (startet eine eigene PostgreSQL-Instanz) |
|
| `pnpm test` | Unit- und Integrationstests (startet eine eigene PostgreSQL-Instanz) |
|
||||||
| `pnpm test:e2e` | Playwright: Smartphone, Tablet, Desktop, axe-Barrierefreiheit (gegen laufenden Server) |
|
| `pnpm test:e2e` | Playwright: Smartphone, Tablet, Desktop, axe-Barrierefreiheit (gegen laufenden Server) |
|
||||||
|
| `pnpm db:dev` | Eingebettetes PostgreSQL für die Entwicklung (Port 54329) |
|
||||||
| `pnpm db:migrate` / `pnpm db:deploy` | Migration erstellen / anwenden |
|
| `pnpm db:migrate` / `pnpm db:deploy` | Migration erstellen / anwenden |
|
||||||
|
| `pnpm db:seed` | Nur Entwicklung: Early Bird simulieren, Test-Gutschein `TEST-STAMMMODEL` |
|
||||||
| `pnpm worker` | Einmaliger Worker-Lauf |
|
| `pnpm worker` | Einmaliger Worker-Lauf |
|
||||||
| `pnpm admin:create` | Admin-Konto anlegen (kein Standardpasswort) |
|
| `pnpm admin:create` | Admin-Konto anlegen (kein Standardpasswort) |
|
||||||
|
| `pnpm media:optimize <ordner>` | Videos und große Bilder vor dem Hochladen aufbereiten (siehe docs/MEDIEN.md) |
|
||||||
|
|
||||||
|
Bei jedem Push auf `main` prüft die CI (`.gitea/workflows/docker-build.yml`) zuerst `lint` samt `pnpm audit`, `typecheck` und `test`; erst wenn alle drei grün sind, baut sie die beiden Images. Die Playwright-Tests laufen nicht in der CI.
|
||||||
|
|
||||||
## Wo was gepflegt wird
|
## Wo was gepflegt wird
|
||||||
|
|
||||||
@@ -59,4 +70,5 @@ Testkarte `4242 4242 4242 4242`, für 3-D-Secure-Fälle `4000 0027 6000 3184`. E
|
|||||||
- **Feste Bilder:** `public/images/unterkunft/` (Villa, aus dem Booking.com-Eintrag, Nutzung vom Auftraggeber freigegeben).
|
- **Feste Bilder:** `public/images/unterkunft/` (Villa, aus dem Booking.com-Eintrag, Nutzung vom Auftraggeber freigegeben).
|
||||||
- **Logo, Headerbild, Porträts, Reels:** feste Dateinamen im Medienverzeichnis, werden automatisch erkannt. Liste: [docs/MEDIEN.md](docs/MEDIEN.md).
|
- **Logo, Headerbild, Porträts, Reels:** feste Dateinamen im Medienverzeichnis, werden automatisch erkannt. Liste: [docs/MEDIEN.md](docs/MEDIEN.md).
|
||||||
- **Widerrufsbelehrung:** Adminbereich → Einstellungen, erscheint unter `/widerruf`.
|
- **Widerrufsbelehrung:** Adminbereich → Einstellungen, erscheint unter `/widerruf`.
|
||||||
|
- **Empfänger des Formulars „Erstgespräch“:** die Veranstalter-E-Mail aus dem Adminbereich, ersatzweise `ADMIN_NOTIFY_EMAIL`. Ist keine von beiden gesetzt, lehnt das Formular Anfragen ab.
|
||||||
- **Weitere Medien:** Dateien ins Medienverzeichnis (`MEDIA_DIR`) legen und in `site.ts` als `{ kind: "image", src: "/media/…" }` eintragen.
|
- **Weitere Medien:** Dateien ins Medienverzeichnis (`MEDIA_DIR`) legen und in `site.ts` als `{ kind: "image", src: "/media/…" }` eintragen.
|
||||||
@@ -92,3 +92,15 @@ Der gehostete Checkout bildet den Ablauf vollständig ab: Betrag, Ablaufzeit, da
|
|||||||
## ADR-7: Adminbereich
|
## ADR-7: Adminbereich
|
||||||
|
|
||||||
Better Auth übernimmt E-Mail und Passwort, Registrierung ist ausgeschaltet. Konten entstehen nur über die Kommandozeile, TOTP-2FA ist Pflicht. Rate Limits der Anmeldung liegen in PostgreSQL, Passwörter werden gegen Datenlecks geprüft. Jede Seite, jede Server-Action und der CSV-Export prüfen Sitzung und 2FA selbst. Der Proxy leitet nur vorab um. Alle Buchungs-, Zahlungs- und Konfigurationsaktionen landen im AuditLog.
|
Better Auth übernimmt E-Mail und Passwort, Registrierung ist ausgeschaltet. Konten entstehen nur über die Kommandozeile, TOTP-2FA ist Pflicht. Rate Limits der Anmeldung liegen in PostgreSQL, Passwörter werden gegen Datenlecks geprüft. Jede Seite, jede Server-Action und der CSV-Export prüfen Sitzung und 2FA selbst. Der Proxy leitet nur vorab um. Alle Buchungs-, Zahlungs- und Konfigurationsaktionen landen im AuditLog.
|
||||||
|
|
||||||
|
## ADR-8: Kontaktformular „Erstgespräch“ (seit 06.10.2026)
|
||||||
|
|
||||||
|
- Eigener Abschnitt nach den Fragen (Anker `#erstgespraech`, `POST /api/contact`). Pflicht sind Name, E-Mail, Telefon (seit 07.10.2026, gleiche Regel wie bei der Buchung) und die Bestätigung der Datenschutzhinweise; Erreichbarkeit und Nachricht sind optional.
|
||||||
|
- Kein eigenes Datenmodell: Die Anfrage geht als E-Mail über den transaktionalen Postausgang an die Veranstalter-E-Mail aus dem Admin, ersatzweise `ADMIN_NOTIFY_EMAIL`. Ohne Empfänger lehnt das Formular ab.
|
||||||
|
- Bewusst keine automatische Bestätigungsmail an die angegebene Adresse, damit das Formular nicht zum Versand an Dritte missbraucht werden kann.
|
||||||
|
- Schutz wie bei der Buchung: nur JSON von der eigenen Seite, Rate Limit 5 Anfragen je Stunde und IP, Honeypot-Feld gegen Bots, doppeltes Absenden in derselben Minute ergibt eine Mail.
|
||||||
|
|
||||||
|
## ADR-9: Client-IP und Rate Limits (seit 12.10.2026)
|
||||||
|
|
||||||
|
- Rate Limits liegen in PostgreSQL (`RateLimitBucket`, gehashter IP-Schlüssel, Aufräumen nach einem Tag durch den Worker). IPv6-Adressen zählen je /64-Netz.
|
||||||
|
- Die Client-IP ist die Gegenstelle laut Traefik (letzter `X-Forwarded-For`-Eintrag). `CF-Connecting-IP` gilt nur als öffentliche Unicast-Adresse, `X-Real-IP` ohne `X-Forwarded-For` nur außerhalb der Produktion. Better Auth bekommt die IP ausschließlich über den serverseitig gesetzten Header `x-artista-client-ip`; ein vom Browser mitgeschickter Wert wird verworfen.
|
||||||
+22
-12
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
## 1. Images bauen
|
## 1. Images bauen
|
||||||
|
|
||||||
**Automatisch (Gitea Actions):** `.gitea/workflows/docker-build.yml` baut bei jedem Push auf `main`, bei Tags `v*` oder manuell per „Run workflow“ beide Images und pusht sie in die Gitea-Registry:
|
**Automatisch (Gitea Actions):** `.gitea/workflows/docker-build.yml` läuft bei jedem Push auf `main`, bei Tags `v*` oder manuell per „Run workflow“. Zuerst prüfen drei Jobs auf dem isolierten Runner (`ci-isolated`, ohne Docker-Socket) den Code: `lint` (inklusive `pnpm audit --prod --audit-level high`), `typecheck` und `test` (mit PostgreSQL 18.6 als Service). Nur wenn alle drei grün sind, baut `build-and-push` auf dem Runner `docker-builder` beide Images und pusht sie in die Gitea-Registry:
|
||||||
|
|
||||||
- `main` ergibt die Tags `latest` und `sha-<kurz>`
|
- `main` ergibt die Tags `latest` und `sha-<kurz>`
|
||||||
- `v0.1.0` ergibt zusätzlich den Tag `0.1.0`
|
- `v0.1.0` ergibt zusätzlich den Tag `0.1.0`
|
||||||
|
|
||||||
Einmalig einrichten:
|
Einmalig einrichten:
|
||||||
- ein Personal Access Token mit Scope `package` als Repo-Secret `REGISTRY_TOKEN` hinterlegen (Repo-Einstellungen → Actions → Secrets)
|
- ein Personal Access Token mit Scope `package` als Repo-Secret `REGISTRY_TOKEN` hinterlegen (Repo-Einstellungen → Actions → Secrets)
|
||||||
- einen Runner mit dem Label `docker-builder` bereitstellen (wie bei Wabenhain)
|
- Runner mit den Labels `ci-isolated` (Prüfungen) und `docker-builder` (Image-Build) bereitstellen; im EnCry-Cluster sind beide vorhanden
|
||||||
|
|
||||||
Die Manifeste in `k8s/` nutzen den Tag `latest`. Für feste Versionen dort `:latest` durch z. B. `:0.1.0` ersetzen (`git tag v0.1.0 && git push origin v0.1.0`).
|
Die Manifeste in `k8s/` sind auf einen CI-Build gepinnt (`sha-<commit>`, Platzhalter `sha-REPLACE_ME` im Repo), nicht auf `latest`. Ausgerollt wird mit `kubectl set image`, siehe Abschnitt 3 und [k8s/README.md](../k8s/README.md).
|
||||||
|
|
||||||
|
Scheitert `build-and-push` beim Push mit `500 … cannot allocate memory`, lag das bisher an der Gitea-Registry, wenn gleichzeitig ein anderes Repo pushte. Dann den Lauf neu starten, sobald der andere Build fertig ist.
|
||||||
|
|
||||||
**Manuell:**
|
**Manuell:**
|
||||||
|
|
||||||
@@ -29,24 +31,32 @@ Der Build braucht Internet für `next/font` (Schriften werden eingebettet), aber
|
|||||||
- der Worker-CronJob, nötig für Reservierungsfreigabe, Abgleich der Folgeraten und Mail-Wiederholung
|
- der Worker-CronJob, nötig für Reservierungsfreigabe, Abgleich der Folgeraten und Mail-Wiederholung
|
||||||
- ConfigMap
|
- ConfigMap
|
||||||
- zwei NetworkPolicies (Schutz der Rate Limits und der Datenbank)
|
- zwei NetworkPolicies (Schutz der Rate Limits und der Datenbank)
|
||||||
|
- der Sicherungs-CronJob `artista-db-backup` (täglicher `pg_dump`, Abschnitt 7)
|
||||||
|
|
||||||
Eine eigene Sicherung im Cluster gibt es nicht, die SMB-Freigabe wird auf dem NAS gesichert.
|
**Schritt für Schritt zum Kopieren: [k8s/README.md](../k8s/README.md).** Die Manifeste liegen flach in `k8s/` (`00-namespace` bis `09-db-backup-cronjob`, Namensschema wie bei Wabenhain) und werden in dieser Reihenfolge einzeln angewendet. Die Secrets kommen aus `k8s/02-secret.yaml.example` und werden nicht eingecheckt. Pod Security: Erzwungen wird `baseline`, damit der Zertifikats-Solver von cert-manager starten kann. `restricted` wird nur gemeldet, die eigenen Pods erfüllen es.
|
||||||
|
|
||||||
**Schritt für Schritt zum Kopieren: [k8s/README.md](../k8s/README.md).** Die Manifeste liegen flach in `k8s/` (`00-namespace` bis `08-networkpolicy`, Namensschema wie bei Wabenhain) und werden in dieser Reihenfolge einzeln angewendet. Die Secrets kommen aus `k8s/02-secret.yaml.example` und werden nicht eingecheckt. Pod Security: Erzwungen wird `baseline`, damit der Zertifikats-Solver von cert-manager starten kann. `restricted` wird nur gemeldet, die eigenen Pods erfüllen es.
|
Im EnCry-Cluster liegen die SMB-Freigaben auf der Hetzner Storage Box, nicht auf einem lokalen NAS; „NAS“ meint im Folgenden diese Freigabe. Im Namespace `artista` läuft außerdem Artista-Telefonie (eigenes Repo `Artista-TeleTracking`). Sie nutzt eine eigene Datenbank `telefonie` in derselben PostgreSQL-Instanz `artista-modelcamp-postgres`. Wer die Instanz oder den Namespace anfasst, trifft beide Anwendungen.
|
||||||
|
|
||||||
Zu PostgreSQL:
|
Zu PostgreSQL:
|
||||||
- **PostgreSQL auf SMB ist eine bewusste Ausnahme.** SMB garantiert Sperren und fsync nicht in jedem Fall wie ein lokales Dateisystem. Bei einem NAS- oder Netzwerkausfall während Schreibvorgängen kann die Datenbank beschädigt werden.
|
- **PostgreSQL auf SMB ist eine bewusste Ausnahme.** SMB garantiert Sperren und fsync nicht in jedem Fall wie ein lokales Dateisystem. Bei einem NAS- oder Netzwerkausfall während Schreibvorgängen kann die Datenbank beschädigt werden.
|
||||||
- **Gemildert** wird das durch `hard`, `cache=strict`, SMB-Verschlüsselung (`seal`) und ein 120 Sekunden langes, sauberes Herunterfahren. Die Rechte setzen die `mountOptions`, weil `fsGroup` auf SMB nicht wirkt: Postgres `uid=70`, `0700`; Medien `uid=1001`, nur lesbar.
|
- **Gemildert** wird das durch `hard`, `cache=strict`, SMB-Verschlüsselung (`seal`) und ein 120 Sekunden langes, sauberes Herunterfahren. Die Rechte setzen die `mountOptions`, weil `fsGroup` auf SMB nicht wirkt: Postgres `uid=70`, `0700`; Medien `uid=1001`, nur lesbar.
|
||||||
- **Sicherung:** Die NAS-Sicherung ist für die laufende Datenbank nur mit **atomaren Snapshots** der Freigabe (Btrfs/ZFS) konsistent. Zusätzlicher Schutz bei Bedarf: gelegentlich einen `pg_dump` ziehen (Abschnitt 7).
|
- **Sicherung:** Eine Kopie der Freigabe ist für die laufende Datenbank nur mit **atomaren Snapshots** (Btrfs/ZFS) konsistent. Deshalb zieht der CronJob `artista-db-backup` jede Nacht einen `pg_dump` (Abschnitt 7).
|
||||||
|
- **Nach SMB-Unterbrechungen** melden lang laufende Postgres-Prozesse gelegentlich `could not seek to end of file … Bad file descriptor`. Abhilfe: `kubectl -n artista rollout restart sts/artista-modelcamp-postgres` (fährt sauber herunter).
|
||||||
|
|
||||||
## 3. Update ausrollen
|
## 3. Update ausrollen
|
||||||
|
|
||||||
|
App, Migration und Worker immer mit demselben Tag `sha-<commit>` aus der CI ausrollen (genauer Befehl in [k8s/README.md](../k8s/README.md), Abschnitt „Bei einem neuen Image“):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl -n artista rollout restart deploy/artista-modelcamp-app
|
TAG=sha-<commit>
|
||||||
|
kubectl -n artista set image deployment/artista-modelcamp-app migrate=git.encry.de/nick.rosenloecher/artista-modelcamp-tools:$TAG app=git.encry.de/nick.rosenloecher/artista-modelcamp:$TAG
|
||||||
|
kubectl -n artista set image cronjob/artista-modelcamp-worker worker=git.encry.de/nick.rosenloecher/artista-modelcamp-tools:$TAG
|
||||||
kubectl -n artista rollout status deploy/artista-modelcamp-app
|
kubectl -n artista rollout status deploy/artista-modelcamp-app
|
||||||
kubectl -n artista logs deploy/artista-modelcamp-app -c migrate # Migrationsausgabe
|
kubectl -n artista logs deploy/artista-modelcamp-app -c migrate # Migrationsausgabe
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Ein `rollout restart` allein holt kein neues Image, weil die Manifeste auf einen festen Tag zeigen.
|
||||||
|
|
||||||
Jeder neue App-Pod migriert per initContainer zuerst die Datenbank. Scheitert das, startet der Pod nicht, und die alten Pods laufen weiter.
|
Jeder neue App-Pod migriert per initContainer zuerst die Datenbank. Scheitert das, startet der Pod nicht, und die alten Pods laufen weiter.
|
||||||
|
|
||||||
Migrationen sind vorwärtskompatibel zu schreiben (erst Spalten ergänzen, später entfernen), damit alte und neue Pods während des Rollouts parallel laufen können.
|
Migrationen sind vorwärtskompatibel zu schreiben (erst Spalten ergänzen, später entfernen), damit alte und neue Pods während des Rollouts parallel laufen können.
|
||||||
@@ -85,10 +95,10 @@ Ein fehlgeschlagener Lauf erscheint als fehlgeschlagener Job (`kubectl -n artist
|
|||||||
|
|
||||||
## 7. Sicherung und Wiederherstellung
|
## 7. Sicherung und Wiederherstellung
|
||||||
|
|
||||||
- **Sicherung:** Die NAS-Sicherung der SMB-Freigabe übernimmt das Speichersystem. Für PostgreSQL gilt das nur mit atomaren Snapshots, siehe Abschnitt 2. Im Cluster läuft kein Backup-Job.
|
- **Täglicher Dump im Cluster:** Der CronJob `artista-db-backup` (`k8s/09-db-backup-cronjob.yaml`) läuft um 02:25 Uhr (Zeitzone des Clusters) und schreibt `pg_dump -Fc` der Datenbank `artista` in die PVC `artista-db-backup` (StorageClass `smb-backup`, Ordner `Backups/artista/` auf der Storage Box). Aufbewahrung: 14 Tage täglich (`daily/`), 8 Wochen sonntags (`weekly/`). Die Datenbank `telefonie` derselben Instanz ist darin nicht enthalten. Stand prüfen: `kubectl -n artista get cronjob artista-db-backup` und `kubectl -n artista get jobs | grep artista-db-backup`.
|
||||||
- **Logischer Dump** (empfohlen vor Updates und vor dem Live-Verkauf):
|
- **Logischer Dump von Hand** (zusätzlich vor Updates und vor dem Live-Verkauf):
|
||||||
```bash
|
```bash
|
||||||
POD=$(kubectl -n artista get pod -l app.kubernetes.io/name=artista-modelcamp-postgres -o name | head -1)
|
POD=$(kubectl -n artista get pod -l app=artista-modelcamp-postgres -o name | head -1)
|
||||||
kubectl -n artista exec $POD -- pg_dump -U artista -d artista --format=custom > artista-$(date +%F).dump
|
kubectl -n artista exec $POD -- pg_dump -U artista -d artista --format=custom > artista-$(date +%F).dump
|
||||||
```
|
```
|
||||||
- **Wiederherstellen:**
|
- **Wiederherstellen:**
|
||||||
@@ -96,8 +106,8 @@ Ein fehlgeschlagener Lauf erscheint als fehlgeschlagener Job (`kubectl -n artist
|
|||||||
2. Einspielen: `kubectl -n artista exec -i $POD -- pg_restore -U artista -d artista --clean --if-exists < artista-YYYY-MM-DD.dump`
|
2. Einspielen: `kubectl -n artista exec -i $POD -- pg_restore -U artista -d artista --clean --if-exists < artista-YYYY-MM-DD.dump`
|
||||||
3. App wieder hochskalieren (der initContainer migriert dabei), Worker fortsetzen.
|
3. App wieder hochskalieren (der initContainer migriert dabei), Worker fortsetzen.
|
||||||
4. Abgleich: Zahlungen, die nach dem Sicherungszeitpunkt eingingen, im Stripe-Dashboard prüfen. Stripe stellt Webhooks bis zu drei Tage erneut zu (Dashboard → Webhooks → „Erneut senden“).
|
4. Abgleich: Zahlungen, die nach dem Sicherungszeitpunkt eingingen, im Stripe-Dashboard prüfen. Stripe stellt Webhooks bis zu drei Tage erneut zu (Dashboard → Webhooks → „Erneut senden“).
|
||||||
- **Medien:** liegen im SMB-Ordner `modelcamp-media` und sind in der NAS-Sicherung enthalten.
|
- **Medien:** liegen im SMB-Ordner `modelcamp-media`; gesichert sind sie nur über die Sicherung der Freigabe selbst.
|
||||||
- **Wiederherstellung üben:** mindestens einmal vor dem Live-Verkauf in einen Test-Namespace.
|
- **Wiederherstellung üben:** mindestens einmal vor dem Live-Verkauf in einen Test-Namespace. Das ist noch nicht geschehen.
|
||||||
|
|
||||||
## 8. Stornierung und Erstattung
|
## 8. Stornierung und Erstattung
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Noch benötigte Inhalte und Freigaben
|
# Noch benötigte Inhalte und Freigaben
|
||||||
|
|
||||||
Stand 01.10.2026. Bereits bestätigt und per Migration `20261001120000_camp_2027_bestaetigt` eingetragen (im Admin änderbar):
|
Stand 16.10.2026. Dieselben Punkte stehen mit Verweis auf die Detail-Issues in der Gitea-Sammelliste [#32 „Inhaber-Aufgaben“](https://git.encry.de/Nick.Rosenloecher/Artista-Filmcamp/issues/32).
|
||||||
|
|
||||||
|
Bereits bestätigt und per Migration `20261001120000_camp_2027_bestaetigt` bzw. `20261002090000_termin_mai_2027` eingetragen (im Admin änderbar). Damit sind im Admin die Freigaben Termin, Ort, Unterkunft und Platzzahl gesetzt, sofern sie dort niemand zurückgenommen hat:
|
||||||
|
|
||||||
- Termin 03.05. bis 09.05.2027 (laut Wochenplan vom 02.10.2026), Villa La Chiesina, Arcidosso (Toskana)
|
- Termin 03.05. bis 09.05.2027 (laut Wochenplan vom 02.10.2026), Villa La Chiesina, Arcidosso (Toskana)
|
||||||
- Unterbringung: eigenes Bett im geteilten Zimmer, keine Einzelzimmer
|
- Unterbringung: eigenes Bett im geteilten Zimmer, keine Einzelzimmer
|
||||||
@@ -9,7 +11,7 @@ Stand 01.10.2026. Bereits bestätigt und per Migration `20261001120000_camp_2027
|
|||||||
- Tagesablauf aller sieben Tage, Bustransfer ab und bis Florenz, Verpflegung (Frühstück, Snacks, Abendessen)
|
- Tagesablauf aller sieben Tage, Bustransfer ab und bis Florenz, Verpflegung (Frühstück, Snacks, Abendessen)
|
||||||
- Early Bird 01.11. bis 31.12.2026, ohne Anzeige eines Verkaufsstarts
|
- Early Bird 01.11. bis 31.12.2026, ohne Anzeige eines Verkaufsstarts
|
||||||
|
|
||||||
Solange die mit **(Freigabe)** markierten Punkte fehlen, bleibt der Live-Verkauf serverseitig gesperrt. Der aktuelle Stand steht im Admin-Dashboard unter „Fehlt für den Live-Verkauf“.
|
Solange die mit **(Freigabe)** markierten Punkte fehlen, bleibt der Live-Verkauf serverseitig gesperrt. Der aktuelle Stand steht im Admin-Dashboard unter „Fehlt für den Live-Verkauf“. Sind alle Freigaben erteilt und ist der Live-Verkauf eingeschaltet, startet der Verkauf sofort zum Early-Bird-Preis; die Early-Bird-Phase läuft vom 01.11. bis 31.12.2026.
|
||||||
|
|
||||||
## Inhalte
|
## Inhalte
|
||||||
|
|
||||||
@@ -40,7 +42,8 @@ Solange die mit **(Freigabe)** markierten Punkte fehlen, bleibt der Live-Verkauf
|
|||||||
|
|
||||||
## Betrieb
|
## Betrieb
|
||||||
|
|
||||||
- Stripe-Konto live verifiziert, Webhook-Endpunkte (Test und Live) mit Secrets angelegt.
|
- Stripe-Konto live verifiziert, Webhook-Endpunkte (Test und Live) mit Secrets angelegt, Billing-Einstellungen für Folgeraten gesetzt (docs/BETRIEB.md, Abschnitt 4).
|
||||||
- SMTP-Anbieter sowie SPF, DKIM und DMARC der Absenderdomain.
|
- SMTP-Anbieter sowie SPF, DKIM und DMARC der Absenderdomain; Veranstalter-E-Mail oder `ADMIN_NOTIFY_EMAIL` gesetzt, sonst lehnt das Formular „Erstgespräch“ Anfragen ab.
|
||||||
- Domain, TLS und StorageClasses im Cluster (siehe docs/BETRIEB.md).
|
- Erledigt: Domain `modelcamp.artistasphotography.de`, TLS über cert-manager, SMB-Speicher und die StorageClass `smb-backup` im Cluster.
|
||||||
- Wiederherstellung aus der Sicherung einmal geübt.
|
- Wiederherstellung aus der Sicherung einmal geübt (noch offen).
|
||||||
|
- Optional: Google-Rezensionen (`GOOGLE_PLACES_API_KEY`, `GOOGLE_PLACE_ID` im Secret); ohne Werte bleibt der Abschnitt ausgeblendet.
|
||||||
+11
-6
@@ -1,10 +1,14 @@
|
|||||||
# Tests und Abnahme
|
# Tests und Abnahme
|
||||||
|
|
||||||
Stand 27.09.2026. Alle aufgeführten Läufe sind lokal ausgeführt und bestanden.
|
Stand 16.10.2026. Die Abschnitte unten sind chronologisch gewachsen; der aktuelle Stand steht hier oben.
|
||||||
|
|
||||||
|
**Aktuell:** `pnpm test` umfasst 10 Testdateien (rund 130 Tests; 132 beim lokalen Lauf am 14.10.2026 zu Commit `f29dbcf`, seither kam mit `9785eb2` ein Regressionstest hinzu). Seit `0421d7d` (14.10.2026) laufen `lint` mit `pnpm audit`, `typecheck` und `test` bei jedem Push auf `main` in der CI; der Test-Job nutzt PostgreSQL 18.6 als Service (`TEST_DATABASE_URL`). Letzter CI-Lauf (`9785eb2`, 15.10.2026): lint, typecheck und test grün, nur der Image-Build schlug fehl (Issue #20). Die Playwright-Tests (`pnpm test:e2e`, 3 Tests je Profil) laufen nur lokal.
|
||||||
|
|
||||||
|
Seit dem ersten Stand hinzugekommene Testdateien: `client-ip.test.ts` (Client-IP hinter Traefik und Cloudflare, IPv6 je /64, Proxy um NextRequest), `contact.integration.test.ts` (Formular „Erstgespräch“), `email-outbox.integration.test.ts` (Postausgang, endgültige und vorübergehende SMTP-Fehler), `review-format.test.ts` (Google-Rezensionen).
|
||||||
|
|
||||||
## Automatisierte Tests
|
## Automatisierte Tests
|
||||||
|
|
||||||
`pnpm test` umfasst 72 Tests in 6 Dateien. Sie laufen gegen eine echte, frisch gestartete PostgreSQL-18-Instanz, damit Zeilensperren und Trigger mitgetestet werden.
|
Stand 27.09.2026: `pnpm test` umfasste 72 Tests in 6 Dateien. Sie laufen gegen eine echte, frisch gestartete PostgreSQL-18-Instanz, damit Zeilensperren und Trigger mitgetestet werden.
|
||||||
|
|
||||||
| Geschäftsrisiko | Datei |
|
| Geschäftsrisiko | Datei |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -33,7 +37,7 @@ Stand 27.09.2026. Alle aufgeführten Läufe sind lokal ausgeführt und bestanden
|
|||||||
|
|
||||||
Bildschirmfotos aller drei Größen wurden visuell geprüft.
|
Bildschirmfotos aller drei Größen wurden visuell geprüft.
|
||||||
|
|
||||||
Letzter Lauf 01.10.2026 nach Einbau von Team, Villa-Bildern und bestätigtem Termin: 81/81 Unit- und Integrationstests, 9/9 E2E-Tests, Produktionsbuild grün. Team und Unterkunft wurden zusätzlich per Bildschirmfoto auf Smartphone (390 px) und Desktop (1440 px) geprüft. Mit Termin 08.05.2027 fällt die letzte Rate auf den 08.04.2027.
|
Letzter Lauf 01.10.2026 nach Einbau von Team, Villa-Bildern und bestätigtem Termin: 81/81 Unit- und Integrationstests, 9/9 E2E-Tests, Produktionsbuild grün. Team und Unterkunft wurden zusätzlich per Bildschirmfoto auf Smartphone (390 px) und Desktop (1440 px) geprüft. Mit dem damaligen Termin 08.05.2027 fiel die letzte Rate auf den 08.04.2027; seit dem Termin 03.05.2027 (Migration vom 02.10.2026) ist es der 03.04.2027.
|
||||||
|
|
||||||
Hinweis: Läuft der Testserver nicht unter der Adresse aus `APP_URL`, lehnt der Same-Origin-Schutz die Preisabfrage ab (403). Dann hängt der Buchungsdialog bei „Preise werden geladen …“. Server deshalb mit passender `APP_URL` starten, z. B. `APP_URL=http://localhost:3005 PORT=3005 pnpm start` und `E2E_BASE_URL=http://localhost:3005 pnpm test:e2e`.
|
Hinweis: Läuft der Testserver nicht unter der Adresse aus `APP_URL`, lehnt der Same-Origin-Schutz die Preisabfrage ab (403). Dann hängt der Buchungsdialog bei „Preise werden geladen …“. Server deshalb mit passender `APP_URL` starten, z. B. `APP_URL=http://localhost:3005 PORT=3005 pnpm start` und `E2E_BASE_URL=http://localhost:3005 pnpm test:e2e`.
|
||||||
|
|
||||||
@@ -53,7 +57,8 @@ Lauf 06.10.2026 nach Umstellung der Folgeraten auf Stripe Billing (inkl. Anhalte
|
|||||||
|
|
||||||
## Nicht ausgeführt / Einschränkungen
|
## Nicht ausgeführt / Einschränkungen
|
||||||
|
|
||||||
- **Echte Stripe-Testbuchung** gegen die Stripe-API: Ein Stripe-Testschlüssel lag nicht vor. Die Stripe-Logik ist gegen eine Attrappe mit Stripe-Semantik getestet (Idempotenz, Session-Zustände, Rechnungen des Ratenplans), die Webhook-Signatur mit der echten Stripe-Bibliothek. Mit Schlüssel: Anleitung in der README (`stripe listen`, Testkarten).
|
- **Vollständige Stripe-Testbuchung** über die Oberfläche bis zum Webhook: In der Testsuite läuft die Stripe-Logik gegen eine Attrappe mit Stripe-Semantik (Idempotenz, Session-Zustände, Rechnungen des Ratenplans), die Webhook-Signatur mit der echten Stripe-Bibliothek. Gegen die echte Stripe-Test-API geprüft ist bisher nur der Ratenplan (`StripeGateway` mit Testuhr, Lauf vom 06.10.2026). Ein dokumentierter Durchlauf Buchung → Checkout → Webhook → Bestätigungsmail auf dem Cluster fehlt (Issue #27). Anleitung lokal in der README (`stripe listen`, Testkarten).
|
||||||
- **Docker-Image und Cluster:** Auf dem Entwicklungsrechner gibt es kein Docker und keinen Cluster-Zugang. Das Dockerfile ist nicht gebaut und nicht im Cluster ausgerollt. Geprüft sind der Standalone-Build und das Rendern der Manifeste.
|
- **Docker-Image und Cluster:** Seit dem 27.09.2026 baut die CI beide Images; die Seite läuft im Cluster (zuletzt belegt ausgerollt: `sha-5a88f3d`, 13.10.2026). Eine automatisierte Prüfung nach dem Rollout gibt es nicht; der Monitoring-Dienst Gatus im Cluster prüft die Erreichbarkeit.
|
||||||
- **SMTP-Versand:** nur über den Datei-Testausgang geprüft.
|
- **SMTP-Versand:** im Repo nur über den Datei-Testausgang geprüft. Ob im Cluster ein echter SMTP-Zugang eingetragen ist, ist hier nicht dokumentiert.
|
||||||
|
- **E2E-Tests** laufen nicht in der CI.
|
||||||
- **Referenzseite:** nur über eine Textzusammenfassung ausgewertet (siehe ARCHITEKTUR.md).
|
- **Referenzseite:** nur über eine Textzusammenfassung ausgewertet (siehe ARCHITEKTUR.md).
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Alle 10 Minuten: faellige Raten einziehen, abgelaufene Reservierungen
|
# Alle 10 Minuten: abgelaufene Reservierungen freigeben, fehlende
|
||||||
# freigeben, E-Mails nachsenden. Ohne ihn funktioniert Ratenzahlung nicht.
|
# Stripe-Ratenplaene anlegen, ueberfaellige Raten mit Stripe abgleichen,
|
||||||
|
# E-Mails nachsenden (Einzug der Folgeraten macht Stripe Billing,
|
||||||
|
# docs/ARCHITEKTUR.md ADR-3). Ohne ihn funktioniert Ratenzahlung nicht.
|
||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: CronJob
|
kind: CronJob
|
||||||
metadata:
|
metadata:
|
||||||
|
|||||||
+8
-2
@@ -4,6 +4,7 @@ Rohe Manifeste wie bei Wabenhain, Nummerierung = Reihenfolge. Voraussetzungen im
|
|||||||
- **Traefik** als Ingress-Controller (IngressClass `traefik`)
|
- **Traefik** als Ingress-Controller (IngressClass `traefik`)
|
||||||
- **cert-manager** mit dem ClusterIssuer `letsencrypt-prod`
|
- **cert-manager** mit dem ClusterIssuer `letsencrypt-prod`
|
||||||
- **csi-driver-smb** für die SMB-Volumes
|
- **csi-driver-smb** für die SMB-Volumes
|
||||||
|
- die StorageClass **`smb-backup`** für den Sicherungs-CronJob (`09-db-backup-cronjob.yaml`); im EnCry-Cluster seit 12.10.2026 vorhanden
|
||||||
|
|
||||||
Prüfen:
|
Prüfen:
|
||||||
|
|
||||||
@@ -11,8 +12,11 @@ Prüfen:
|
|||||||
kubectl get ingressclass
|
kubectl get ingressclass
|
||||||
kubectl get clusterissuer letsencrypt-prod
|
kubectl get clusterissuer letsencrypt-prod
|
||||||
kubectl get csidrivers | grep smb
|
kubectl get csidrivers | grep smb
|
||||||
|
kubectl get storageclass smb-backup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Im EnCry-Cluster läuft die Seite unter `modelcamp.artistasphotography.de`. Der DNS-Eintrag ist bei Cloudflare bewusst nicht geproxyt (DNS-only), deshalb bekommt dieser Ingress keine `cloudflare-only`-Middleware. Im selben Namespace läuft Artista-Telefonie (Repo `Artista-TeleTracking`) mit einer eigenen Datenbank `telefonie` in `artista-modelcamp-postgres`.
|
||||||
|
|
||||||
## Einmalige Vorbereitung
|
## Einmalige Vorbereitung
|
||||||
|
|
||||||
1. **Images:** `.gitea/workflows/docker-build.yml` baut bei jedem Push auf `main` die Images `git.encry.de/nick.rosenloecher/artista-modelcamp:latest` und `…-tools:latest`. Voraussetzung ist das Repo-Secret `REGISTRY_TOKEN` (Personal Access Token mit `package`-Scope), wie bei Wabenhain.
|
1. **Images:** `.gitea/workflows/docker-build.yml` baut bei jedem Push auf `main` die Images `git.encry.de/nick.rosenloecher/artista-modelcamp:latest` und `…-tools:latest`. Voraussetzung ist das Repo-Secret `REGISTRY_TOKEN` (Personal Access Token mit `package`-Scope), wie bei Wabenhain.
|
||||||
@@ -22,8 +26,9 @@ kubectl get csidrivers | grep smb
|
|||||||
helm install csi-driver-smb csi-driver-smb/csi-driver-smb -n kube-system
|
helm install csi-driver-smb csi-driver-smb/csi-driver-smb -n kube-system
|
||||||
```
|
```
|
||||||
3. **NAS:** Die Ordner `artista/modelcamp-postgres` (leer) und `artista/modelcamp-media` anlegen und SMB-Verschlüsselung erlauben. `nas.example.lan` in `03-storage.yaml` anpassen.
|
3. **NAS:** Die Ordner `artista/modelcamp-postgres` (leer) und `artista/modelcamp-media` anlegen und SMB-Verschlüsselung erlauben. `nas.example.lan` in `03-storage.yaml` anpassen.
|
||||||
4. **Domain:** `modelcamp.example.org` in `01-configmap.yaml` (APP_URL) und `06-ingress.yaml` (2×) ersetzen. Der DNS-Eintrag muss auf den Cluster zeigen, sonst stellt cert-manager kein Zertifikat aus.
|
4. **Domain:** `modelcamp.example.org` in `01-configmap.yaml` (APP_URL) und `06-ingress.yaml` (2×) ersetzen (im EnCry-Cluster `modelcamp.artistasphotography.de`). Der DNS-Eintrag muss auf den Cluster zeigen, sonst stellt cert-manager kein Zertifikat aus.
|
||||||
5. **Secrets:** `cp k8s/02-secret.yaml.example k8s/02-secret.yaml` und ausfüllen. Die Datei steht in `.gitignore`, bitte **nie** mit echten Werten committen.
|
5. **Secrets:** `cp k8s/02-secret.yaml.example k8s/02-secret.yaml` und ausfüllen. Die Datei steht in `.gitignore`, bitte **nie** mit echten Werten committen.
|
||||||
|
6. **Image-Tag:** In `05-app-deployment.yaml` (2×) und `07-worker-cronjob.yaml` steht der Platzhalter `sha-REPLACE_ME`. Vor dem ersten Anwenden durch den Tag des letzten grünen CI-Laufs ersetzen (`sha-<commit>`, 7 Zeichen) oder nach dem Anwenden wie unter „Bei einem neuen Image“ setzen.
|
||||||
|
|
||||||
## Deployment (auch für einen kompletten Neuaufbau)
|
## Deployment (auch für einen kompletten Neuaufbau)
|
||||||
|
|
||||||
@@ -38,6 +43,7 @@ kubectl apply -f k8s/05-app-deployment.yaml
|
|||||||
kubectl apply -f k8s/06-ingress.yaml
|
kubectl apply -f k8s/06-ingress.yaml
|
||||||
kubectl apply -f k8s/07-worker-cronjob.yaml
|
kubectl apply -f k8s/07-worker-cronjob.yaml
|
||||||
kubectl apply -f k8s/08-networkpolicy.yaml
|
kubectl apply -f k8s/08-networkpolicy.yaml
|
||||||
|
kubectl apply -f k8s/09-db-backup-cronjob.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Die Migration läuft automatisch als initContainer vor dem App-Start. Den Stand zeigen:
|
Die Migration läuft automatisch als initContainer vor dem App-Start. Den Stand zeigen:
|
||||||
@@ -47,7 +53,7 @@ kubectl rollout status deployment/artista-modelcamp-app -n artista
|
|||||||
kubectl get certificate -n artista # READY muss True werden (meist < 2 Minuten)
|
kubectl get certificate -n artista # READY muss True werden (meist < 2 Minuten)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Neuaufbau mit `kubectl delete namespace artista`:** Die PersistentVolumes (`artista-modelcamp-postgres`, `artista-modelcamp-media`) gehören keinem Namespace und bleiben auf `Released` stehen. Die Daten auf dem NAS bleiben erhalten (`Retain`). Damit die neuen Claims sie wieder binden können, vor dem erneuten Anlegen:
|
**Neuaufbau mit `kubectl delete namespace artista`:** Achtung, das löscht auch Artista-Telefonie samt deren Secret; deren Datenbank liegt auf demselben Postgres-Volume. Die PersistentVolumes (`artista-modelcamp-postgres`, `artista-modelcamp-media`) gehören keinem Namespace und bleiben auf `Released` stehen. Die Daten auf dem NAS bleiben erhalten (`Retain`). Damit die neuen Claims sie wieder binden können, vor dem erneuten Anlegen:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
kubectl patch pv artista-modelcamp-postgres -p '{"spec":{"claimRef":null}}'
|
kubectl patch pv artista-modelcamp-postgres -p '{"spec":{"claimRef":null}}'
|
||||||
|
|||||||
+3
-3
@@ -27,14 +27,14 @@
|
|||||||
"@prisma/client": "7.10.0",
|
"@prisma/client": "7.10.0",
|
||||||
"better-auth": "1.7.6",
|
"better-auth": "1.7.6",
|
||||||
"motion": "13.4.4",
|
"motion": "13.4.4",
|
||||||
"next": "16.3.6",
|
"next": "16.3.8",
|
||||||
"nodemailer": "10.0.10",
|
"nodemailer": "10.0.10",
|
||||||
"pg": "8.23.0",
|
"pg": "8.23.0",
|
||||||
"qrcode": "1.5.4",
|
"qrcode": "1.5.4",
|
||||||
"react": "19.3.0",
|
"react": "19.3.0",
|
||||||
"react-dom": "19.3.0",
|
"react-dom": "19.3.0",
|
||||||
"server-only": "0.0.1",
|
"server-only": "0.0.1",
|
||||||
"sharp": "0.35.4",
|
"sharp": "0.35.5",
|
||||||
"stripe": "22.6.2",
|
"stripe": "22.6.2",
|
||||||
"zod": "4.6.5"
|
"zod": "4.6.5"
|
||||||
},
|
},
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"dotenv": "18.0.4",
|
"dotenv": "18.0.4",
|
||||||
"embedded-postgres": "18.4.0-beta.17",
|
"embedded-postgres": "18.4.0-beta.17",
|
||||||
"eslint": "^9.39.5",
|
"eslint": "^9.39.5",
|
||||||
"eslint-config-next": "16.3.6",
|
"eslint-config-next": "16.3.8",
|
||||||
"ffmpeg-static": "5.3.0",
|
"ffmpeg-static": "5.3.0",
|
||||||
"prisma": "7.10.0",
|
"prisma": "7.10.0",
|
||||||
"tailwindcss": "4.3.3",
|
"tailwindcss": "4.3.3",
|
||||||
|
|||||||
Generated
+217
-220
@@ -162,6 +162,12 @@ settings:
|
|||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
mysql2: '>=3.23.1'
|
||||||
|
deepmerge-ts: '>=8.0.0'
|
||||||
|
sharp: '>=0.35.5'
|
||||||
|
source-map-js: '>=1.2.2'
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
@@ -171,16 +177,16 @@ importers:
|
|||||||
version: 7.10.0
|
version: 7.10.0
|
||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: 7.10.0
|
specifier: 7.10.0
|
||||||
version: 7.10.0([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])
|
version: 7.10.0([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.7.6
|
specifier: 1.7.6
|
||||||
version: 1.7.6(@prisma/[email protected]([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))(mysql2@3.15.3)([email protected].6(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])))
|
version: 1.7.6(@prisma/[email protected]([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))(mysql2@3.24.5(@types/[email protected]))([email protected].8(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])))
|
||||||
motion:
|
motion:
|
||||||
specifier: 13.4.4
|
specifier: 13.4.4
|
||||||
version: 13.4.4([email protected]([email protected]))([email protected])
|
version: 13.4.4([email protected]([email protected]))([email protected])
|
||||||
next:
|
next:
|
||||||
specifier: 16.3.6
|
specifier: 16.3.8
|
||||||
version: 16.3.6(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
version: 16.3.8(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 10.0.10
|
specifier: 10.0.10
|
||||||
version: 10.0.10
|
version: 10.0.10
|
||||||
@@ -200,8 +206,8 @@ importers:
|
|||||||
specifier: 0.0.1
|
specifier: 0.0.1
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
sharp:
|
sharp:
|
||||||
specifier: 0.35.4
|
specifier: '>=0.35.5'
|
||||||
version: 0.35.4(@types/[email protected])
|
version: 0.35.5(@types/[email protected])
|
||||||
stripe:
|
stripe:
|
||||||
specifier: 22.6.2
|
specifier: 22.6.2
|
||||||
version: 22.6.2(@types/[email protected])
|
version: 22.6.2(@types/[email protected])
|
||||||
@@ -246,14 +252,14 @@ importers:
|
|||||||
specifier: ^9.39.5
|
specifier: ^9.39.5
|
||||||
version: 9.39.5([email protected])([email protected])
|
version: 9.39.5([email protected])([email protected])
|
||||||
eslint-config-next:
|
eslint-config-next:
|
||||||
specifier: 16.3.6
|
specifier: 16.3.8
|
||||||
version: 16.3.6(@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected])([email protected])
|
version: 16.3.8(@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected])([email protected])
|
||||||
ffmpeg-static:
|
ffmpeg-static:
|
||||||
specifier: 5.3.0
|
specifier: 5.3.0
|
||||||
version: 5.3.0([email protected])
|
version: 5.3.0([email protected])
|
||||||
prisma:
|
prisma:
|
||||||
specifier: 7.10.0
|
specifier: 7.10.0
|
||||||
version: 7.10.0(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
version: 7.10.0(@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: 4.3.3
|
specifier: 4.3.3
|
||||||
version: 4.3.3
|
version: 4.3.3
|
||||||
@@ -726,160 +732,160 @@ packages:
|
|||||||
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
|
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
|
resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
|
resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
|
resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
|
resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
|
resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
|
resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
|
resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
|
resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
|
resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
|
resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
|
resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
|
resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
|
resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
|
resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
|
resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
|
resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
|
resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
|
resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
|
resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
|
resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
|
resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
|
resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
|
resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [wasm32]
|
cpu: [wasm32]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
|
resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
|
resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==}
|
||||||
engines: {node: ^20.9.0}
|
engines: {node: ^20.9.0}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
|
resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -907,60 +913,60 @@ packages:
|
|||||||
'@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4
|
'@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4
|
||||||
'@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4
|
'@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-x9Vblze1EbtltQYnNH38xCPWU3TVfBd1eXqA3+w9+BTpedkkdNpAaltXlGQ/nsc1+E0mVTNrtcbX3GoO09zeLQ==}
|
resolution: {integrity: sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==}
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-jowwDX+7DOlDIjJLgTMxudw+k37QnWu1JkZLkSi9MaJBfDYcfhAPMKBhXL0idYzFN/AGg//axnOR4cLkHX/Rng==}
|
resolution: {integrity: sha512-eCR9RcLTZrVS+K3+VOhi0xMdUUGNuriCHHhU1ZoABC/QQ/Gp0OWbLKQxV2/9DkmihYdYRcQWFmZXW8M1AMUXYw==}
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-E/7GEqaUkt8mk/T8v9lAnrhzR06kdq1ZBkC12F8tAMkdIadwNp3H1KqHynDHrpcTlGCUdq/qu6vUL2aYVyYBdw==}
|
resolution: {integrity: sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-yBE893/nDWTlaiBD1p+qgt7NUen4U5R6FXyH0s67Npq1S3E0cVSef1WIXC2xBRgQvwAvJq6DnS6Y6PrY0cy4Ew==}
|
resolution: {integrity: sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-KJDpjBqBPYlvkivmyrp+Qys6k/7ksbqGQvRVc6ZEGfR+cjQxx+nUkJaWmNZJsmoOrqYNbaXByF8wa0lBwDhB3Q==}
|
resolution: {integrity: sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-mqNg2K+hvWskSRb/QM+Ix412DvBsuSF0XV+frTSw5vmoucNnIlynFwKYew8D01bfATErMOM7Bujrf0BA5DRKFA==}
|
resolution: {integrity: sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-nFncBNGAYouRHjRVaITs9beZRfhX4ssVwpnvPIAbkZVH6LtGoAVlH4bJ8Cnf9SOo9bsXgPFer/GdHtEE3JNOkw==}
|
resolution: {integrity: sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-5Mf3cHDGR/Iz0ng2Bj3zUR3p5QS9YK3Hn2QiAfavFmyF48zwThAjpFoiTKNIcOHLYS4zEk+gzyJ/9deQ2ZB8yQ==}
|
resolution: {integrity: sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-0jkJy0C2kbrJWTk4YLa3xk80pVBpx8FCHJym7CnUfDAXe/FWv5qT7SQJbR0KuemyxaEDlEx5WT4VQJoTW+/9Qw==}
|
resolution: {integrity: sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
resolution: {integrity: sha512-/YXjI1e5OXcZ7YpxRwgP/1jAV/SBKTzeVKqN2mk7mLpcICsyn3Gl5+dIfDTJp70M0ccMhyMMRso4v6mPDCGepg==}
|
resolution: {integrity: sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -1788,7 +1794,7 @@ packages:
|
|||||||
drizzle-kit: '>=0.31.4 || >=1.0.0-beta.1'
|
drizzle-kit: '>=0.31.4 || >=1.0.0-beta.1'
|
||||||
drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0
|
drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0
|
||||||
mongodb: ^6.0.0 || ^7.0.0
|
mongodb: ^6.0.0 || ^7.0.0
|
||||||
mysql2: ^3.0.0
|
mysql2: '>=3.23.1'
|
||||||
next: ^14.0.0 || ^15.0.0 || ^16.0.0
|
next: ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||||
pg: ^8.0.0
|
pg: ^8.0.0
|
||||||
prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
|
prisma: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
@@ -2038,9 +2044,9 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
|
|
||||||
deepmerge-ts@7.1.5:
|
deepmerge-ts@8.0.2:
|
||||||
resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==}
|
resolution: {integrity: sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.9.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
|
||||||
@@ -2056,10 +2062,6 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
|
resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
|
||||||
|
|
||||||
[email protected]:
|
|
||||||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
|
||||||
engines: {node: '>=0.10'}
|
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
|
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
|
||||||
|
|
||||||
@@ -2174,8 +2176,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
[email protected].6:
|
[email protected].8:
|
||||||
resolution: {integrity: sha512-1Upt3U7BDwU+ilpe2byZjAfts9oNq4d4fv/zXEvs8/4yS+cwOQW/WCxUNy8gCDquX67SzeehDvKblVC6ZBMocQ==}
|
resolution: {integrity: sha512-81vovwMe6NGnoFsl0KUJWzlS+y239i3fdsxs49gSEX3pDQW2cuKy+fxUPXNkQqqx6OCPheYPtBlSufc9aZ0k+w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=9.0.0'
|
eslint: '>=9.0.0'
|
||||||
typescript: '>=3.3.1'
|
typescript: '>=3.3.1'
|
||||||
@@ -2956,9 +2958,11 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
mysql2@3.15.3:
|
mysql2@3.24.5:
|
||||||
resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==}
|
resolution: {integrity: sha512-X6Ujsr2QSkkLpkQGjxzpKRAPn9nu4axpR63ntBzquFVEvPOArgbUQ1sJjFKI7hnaYtiVZxa17Z7q18KSubW0IQ==}
|
||||||
engines: {node: '>= 8.0'}
|
engines: {node: '>= 8.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': '>= 8'
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==}
|
resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==}
|
||||||
@@ -2981,8 +2985,8 @@ packages:
|
|||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
[email protected].6:
|
[email protected].8:
|
||||||
resolution: {integrity: sha512-L+otWM/aQbYTx98aZhgEoMb4bZAXx1YVW4UMA/vuCyCoWG5HJyZUili8QAkqzrcC+5///tsz3s0M+SlyB5bLMw==}
|
resolution: {integrity: sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3362,9 +3366,6 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
[email protected]:
|
|
||||||
resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
|
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||||
|
|
||||||
@@ -3386,8 +3387,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
|
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
[email protected].4:
|
[email protected].5:
|
||||||
resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
|
resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/node': '*'
|
'@types/node': '*'
|
||||||
@@ -3429,17 +3430,17 @@ packages:
|
|||||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
[email protected].1:
|
[email protected].2:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||||
engines: {node: '>= 10.x'}
|
engines: {node: '>= 10.x'}
|
||||||
|
|
||||||
sql[email protected]:
|
sql[email protected]:
|
||||||
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
|
resolution: {integrity: sha512-6CKD38c31SENivxOADeMNLdukOnUxUcflKtzVWzace7Riv1v7cAEym5Cx9Q7gYZ3ezIJI7ZpqecQq8cqNeSSFg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
|
resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
|
||||||
@@ -3938,13 +3939,13 @@ snapshots:
|
|||||||
'@better-auth/core': 1.7.6(@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])
|
'@better-auth/core': 1.7.6(@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])
|
||||||
'@better-auth/utils': 0.4.2
|
'@better-auth/utils': 0.4.2
|
||||||
|
|
||||||
'@better-auth/[email protected](@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@prisma/[email protected]([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))':
|
'@better-auth/[email protected](@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@prisma/[email protected]([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.7.6(@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])
|
'@better-auth/core': 1.7.6(@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])
|
||||||
'@better-auth/utils': 0.4.2
|
'@better-auth/utils': 0.4.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@prisma/client': 7.10.0([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])
|
'@prisma/client': 7.10.0([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])
|
||||||
prisma: 7.10.0(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
prisma: 7.10.0(@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
||||||
|
|
||||||
'@better-auth/[email protected](@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@better-fetch/[email protected])':
|
'@better-auth/[email protected](@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@better-fetch/[email protected])':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4171,108 +4172,108 @@ snapshots:
|
|||||||
|
|
||||||
'@img/[email protected]': {}
|
'@img/[email protected]': {}
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-darwin-arm64': 1.3.3
|
'@img/sharp-libvips-darwin-arm64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-darwin-x64': 1.3.3
|
'@img/sharp-libvips-darwin-x64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@img/sharp-wasm32': 0.35.4
|
'@img/sharp-wasm32': 0.35.5
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].3':
|
'@img/[email protected].4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-arm64': 1.3.3
|
'@img/sharp-libvips-linux-arm64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-arm': 1.3.3
|
'@img/sharp-libvips-linux-arm': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-ppc64': 1.3.3
|
'@img/sharp-libvips-linux-ppc64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-riscv64': 1.3.3
|
'@img/sharp-libvips-linux-riscv64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-s390x': 1.3.3
|
'@img/sharp-libvips-linux-s390x': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-x64': 1.3.3
|
'@img/sharp-libvips-linux-x64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linuxmusl-arm64': 1.3.3
|
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linuxmusl-x64': 1.3.3
|
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/runtime': 1.11.3
|
'@emnapi/runtime': 1.11.3
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@img/sharp-wasm32': 0.35.4
|
'@img/sharp-wasm32': 0.35.5
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/[email protected].4':
|
'@img/[email protected].5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@jridgewell/[email protected]':
|
'@jridgewell/[email protected]':
|
||||||
@@ -4301,37 +4302,37 @@ snapshots:
|
|||||||
'@tybys/wasm-util': 0.10.4
|
'@tybys/wasm-util': 0.10.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6': {}
|
'@next/[email protected].8': {}
|
||||||
|
|
||||||
'@next/[email protected].6([email protected]([email protected])([email protected]))':
|
'@next/[email protected].8([email protected]([email protected])([email protected]))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1([email protected]([email protected])([email protected]))
|
'@eslint-community/eslint-utils': 4.9.1([email protected]([email protected])([email protected]))
|
||||||
fast-glob: 3.3.1
|
fast-glob: 3.3.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- eslint
|
- eslint
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/[email protected].6':
|
'@next/[email protected].8':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@noble/[email protected]': {}
|
'@noble/[email protected]': {}
|
||||||
@@ -4371,17 +4372,17 @@ snapshots:
|
|||||||
|
|
||||||
'@prisma/[email protected]': {}
|
'@prisma/[email protected]': {}
|
||||||
|
|
||||||
'@prisma/[email protected]([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])':
|
'@prisma/[email protected]([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/client-runtime-utils': 7.10.0
|
'@prisma/client-runtime-utils': 7.10.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
prisma: 7.10.0(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
prisma: 7.10.0(@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@prisma/[email protected]':
|
'@prisma/[email protected]':
|
||||||
dependencies:
|
dependencies:
|
||||||
c12: 3.3.4
|
c12: 3.3.4
|
||||||
deepmerge-ts: 7.1.5
|
deepmerge-ts: 8.0.2
|
||||||
effect: 3.20.0
|
effect: 3.20.0
|
||||||
empathic: 2.0.0
|
empathic: 2.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -4584,7 +4585,7 @@ snapshots:
|
|||||||
jiti: 2.7.0
|
jiti: 2.7.0
|
||||||
lightningcss: 1.32.0
|
lightningcss: 1.32.0
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
tailwindcss: 4.3.3
|
tailwindcss: 4.3.3
|
||||||
|
|
||||||
'@tailwindcss/[email protected]':
|
'@tailwindcss/[email protected]':
|
||||||
@@ -5142,14 +5143,14 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected](@prisma/[email protected]([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))(mysql2@3.15.3)([email protected].6(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))):
|
[email protected](@prisma/[email protected]([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))(mysql2@3.24.5(@types/[email protected]))([email protected].8(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/core': 1.7.6(@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])
|
'@better-auth/core': 1.7.6(@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])
|
||||||
'@better-auth/drizzle-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])
|
'@better-auth/drizzle-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])
|
||||||
'@better-auth/kysely-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])([email protected])
|
'@better-auth/kysely-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])([email protected])
|
||||||
'@better-auth/memory-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])
|
'@better-auth/memory-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])
|
||||||
'@better-auth/mongo-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])
|
'@better-auth/mongo-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])
|
||||||
'@better-auth/prisma-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@prisma/[email protected]([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))
|
'@better-auth/prisma-adapter': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@prisma/[email protected]([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]))([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))
|
||||||
'@better-auth/telemetry': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@better-fetch/[email protected])
|
'@better-auth/telemetry': 1.7.6(@better-auth/[email protected](@better-auth/[email protected])(@better-fetch/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected]))(@better-auth/[email protected])(@better-fetch/[email protected])
|
||||||
'@better-auth/utils': 0.4.2
|
'@better-auth/utils': 0.4.2
|
||||||
'@better-fetch/fetch': 1.3.2
|
'@better-fetch/fetch': 1.3.2
|
||||||
@@ -5162,11 +5163,11 @@ snapshots:
|
|||||||
nanostores: 1.5.4
|
nanostores: 1.5.4
|
||||||
zod: 4.6.5
|
zod: 4.6.5
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@prisma/client': 7.10.0([email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])
|
'@prisma/client': 7.10.0([email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected])
|
||||||
mysql2: 3.15.3
|
mysql2: 3.24.5(@types/[email protected])
|
||||||
next: 16.3.6(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
next: 16.3.8(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
pg: 8.23.0
|
pg: 8.23.0
|
||||||
prisma: 7.10.0(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
prisma: 7.10.0(@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
||||||
react: 19.3.0
|
react: 19.3.0
|
||||||
react-dom: 19.3.0([email protected])
|
react-dom: 19.3.0([email protected])
|
||||||
vitest: 4.1.11(@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
|
vitest: 4.1.11(@types/[email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected]))
|
||||||
@@ -5379,7 +5380,7 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
deepmerge-ts@7.1.5: {}
|
deepmerge-ts@8.0.2: {}
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5399,8 +5400,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
robust-predicates: 3.0.3
|
robust-predicates: 3.0.3
|
||||||
|
|
||||||
[email protected]: {}
|
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
@@ -5607,9 +5606,9 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected].6(@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected])([email protected]):
|
[email protected].8(@typescript-eslint/[email protected]([email protected]([email protected])([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected])([email protected]):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 16.3.6([email protected]([email protected])([email protected]))
|
'@next/eslint-plugin-next': 16.3.8([email protected]([email protected])([email protected]))
|
||||||
eslint: 9.39.5([email protected])([email protected])
|
eslint: 9.39.5([email protected])([email protected])
|
||||||
eslint-import-resolver-node: 0.3.10([email protected])
|
eslint-import-resolver-node: 0.3.10([email protected])
|
||||||
eslint-import-resolver-typescript: 3.10.1([email protected])([email protected]([email protected])([email protected]))([email protected])
|
eslint-import-resolver-typescript: 3.10.1([email protected])([email protected]([email protected])([email protected]))([email protected])
|
||||||
@@ -6406,17 +6405,16 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
mysql2@3.15.3:
|
mysql2@3.24.5(@types/[email protected]):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@types/node': 22.20.4
|
||||||
aws-ssl-profiles: 1.1.2
|
aws-ssl-profiles: 1.1.2
|
||||||
denque: 2.1.0
|
|
||||||
generate-function: 2.3.1
|
generate-function: 2.3.1
|
||||||
iconv-lite: 0.7.3
|
iconv-lite: 0.7.3
|
||||||
long: 5.3.2
|
long: 5.3.2
|
||||||
lru.min: 1.1.5
|
lru.min: 1.1.5
|
||||||
named-placeholders: 1.1.6
|
named-placeholders: 1.1.6
|
||||||
seq-queue: 0.0.5
|
sql-escaper: 1.5.2
|
||||||
sqlstring: 2.3.3
|
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6430,9 +6428,9 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected].6(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]):
|
[email protected].8(@babel/[email protected]([email protected]))(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 16.3.6
|
'@next/env': 16.3.8
|
||||||
'@swc/helpers': 0.5.23
|
'@swc/helpers': 0.5.23
|
||||||
baseline-browser-mapping: 2.11.26
|
baseline-browser-mapping: 2.11.26
|
||||||
caniuse-lite: 1.0.30001812
|
caniuse-lite: 1.0.30001812
|
||||||
@@ -6441,16 +6439,16 @@ snapshots:
|
|||||||
react-dom: 19.3.0([email protected])
|
react-dom: 19.3.0([email protected])
|
||||||
styled-jsx: 5.1.6(@babel/[email protected]([email protected]))([email protected])
|
styled-jsx: 5.1.6(@babel/[email protected]([email protected]))([email protected])
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 16.3.6
|
'@next/swc-darwin-arm64': 16.3.8
|
||||||
'@next/swc-darwin-x64': 16.3.6
|
'@next/swc-darwin-x64': 16.3.8
|
||||||
'@next/swc-linux-arm64-gnu': 16.3.6
|
'@next/swc-linux-arm64-gnu': 16.3.8
|
||||||
'@next/swc-linux-arm64-musl': 16.3.6
|
'@next/swc-linux-arm64-musl': 16.3.8
|
||||||
'@next/swc-linux-x64-gnu': 16.3.6
|
'@next/swc-linux-x64-gnu': 16.3.8
|
||||||
'@next/swc-linux-x64-musl': 16.3.6
|
'@next/swc-linux-x64-musl': 16.3.8
|
||||||
'@next/swc-win32-arm64-msvc': 16.3.6
|
'@next/swc-win32-arm64-msvc': 16.3.8
|
||||||
'@next/swc-win32-x64-msvc': 16.3.6
|
'@next/swc-win32-x64-msvc': 16.3.8
|
||||||
'@playwright/test': 1.63.0
|
'@playwright/test': 1.63.0
|
||||||
sharp: 0.35.4(@types/[email protected])
|
sharp: 0.35.5(@types/[email protected])
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
@@ -6624,13 +6622,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.19
|
nanoid: 3.3.19
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.19
|
nanoid: 3.3.19
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.2
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
@@ -6648,17 +6646,18 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected](@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]):
|
[email protected](@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])([email protected]):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@prisma/config': 7.10.0
|
'@prisma/config': 7.10.0
|
||||||
'@prisma/dev': 0.24.17([email protected])
|
'@prisma/dev': 0.24.17([email protected])
|
||||||
'@prisma/engines': 7.10.0
|
'@prisma/engines': 7.10.0
|
||||||
'@prisma/studio-core': 0.33.0(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
'@prisma/studio-core': 0.33.0(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||||
mysql2: 3.15.3
|
mysql2: 3.24.5(@types/[email protected])
|
||||||
postgres: 3.4.7
|
postgres: 3.4.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
- '@types/react-dom'
|
- '@types/react-dom'
|
||||||
- magicast
|
- magicast
|
||||||
@@ -6822,8 +6821,6 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
@@ -6852,37 +6849,37 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
es-object-atoms: 1.1.2
|
es-object-atoms: 1.1.2
|
||||||
|
|
||||||
[email protected].4(@types/[email protected]):
|
[email protected].5(@types/[email protected]):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@img/colour': 1.1.0
|
'@img/colour': 1.1.0
|
||||||
detect-libc: 2.1.2
|
detect-libc: 2.1.2
|
||||||
semver: 7.8.5
|
semver: 7.8.5
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-darwin-arm64': 0.35.4
|
'@img/sharp-darwin-arm64': 0.35.5
|
||||||
'@img/sharp-darwin-x64': 0.35.4
|
'@img/sharp-darwin-x64': 0.35.5
|
||||||
'@img/sharp-freebsd-wasm32': 0.35.4
|
'@img/sharp-freebsd-wasm32': 0.35.5
|
||||||
'@img/sharp-libvips-darwin-arm64': 1.3.3
|
'@img/sharp-libvips-darwin-arm64': 1.3.4
|
||||||
'@img/sharp-libvips-darwin-x64': 1.3.3
|
'@img/sharp-libvips-darwin-x64': 1.3.4
|
||||||
'@img/sharp-libvips-linux-arm': 1.3.3
|
'@img/sharp-libvips-linux-arm': 1.3.4
|
||||||
'@img/sharp-libvips-linux-arm64': 1.3.3
|
'@img/sharp-libvips-linux-arm64': 1.3.4
|
||||||
'@img/sharp-libvips-linux-ppc64': 1.3.3
|
'@img/sharp-libvips-linux-ppc64': 1.3.4
|
||||||
'@img/sharp-libvips-linux-riscv64': 1.3.3
|
'@img/sharp-libvips-linux-riscv64': 1.3.4
|
||||||
'@img/sharp-libvips-linux-s390x': 1.3.3
|
'@img/sharp-libvips-linux-s390x': 1.3.4
|
||||||
'@img/sharp-libvips-linux-x64': 1.3.3
|
'@img/sharp-libvips-linux-x64': 1.3.4
|
||||||
'@img/sharp-libvips-linuxmusl-arm64': 1.3.3
|
'@img/sharp-libvips-linuxmusl-arm64': 1.3.4
|
||||||
'@img/sharp-libvips-linuxmusl-x64': 1.3.3
|
'@img/sharp-libvips-linuxmusl-x64': 1.3.4
|
||||||
'@img/sharp-linux-arm': 0.35.4
|
'@img/sharp-linux-arm': 0.35.5
|
||||||
'@img/sharp-linux-arm64': 0.35.4
|
'@img/sharp-linux-arm64': 0.35.5
|
||||||
'@img/sharp-linux-ppc64': 0.35.4
|
'@img/sharp-linux-ppc64': 0.35.5
|
||||||
'@img/sharp-linux-riscv64': 0.35.4
|
'@img/sharp-linux-riscv64': 0.35.5
|
||||||
'@img/sharp-linux-s390x': 0.35.4
|
'@img/sharp-linux-s390x': 0.35.5
|
||||||
'@img/sharp-linux-x64': 0.35.4
|
'@img/sharp-linux-x64': 0.35.5
|
||||||
'@img/sharp-linuxmusl-arm64': 0.35.4
|
'@img/sharp-linuxmusl-arm64': 0.35.5
|
||||||
'@img/sharp-linuxmusl-x64': 0.35.4
|
'@img/sharp-linuxmusl-x64': 0.35.5
|
||||||
'@img/sharp-webcontainers-wasm32': 0.35.4
|
'@img/sharp-webcontainers-wasm32': 0.35.5
|
||||||
'@img/sharp-win32-arm64': 0.35.4
|
'@img/sharp-win32-arm64': 0.35.5
|
||||||
'@img/sharp-win32-ia32': 0.35.4
|
'@img/sharp-win32-ia32': 0.35.5
|
||||||
'@img/sharp-win32-x64': 0.35.4
|
'@img/sharp-win32-x64': 0.35.5
|
||||||
'@types/node': 22.20.4
|
'@types/node': 22.20.4
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
@@ -6925,11 +6922,11 @@ snapshots:
|
|||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
[email protected].1: {}
|
[email protected].2: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
sql[email protected]: {}
|
sql[email protected]: {}
|
||||||
|
|
||||||
[email protected]: {}
|
[email protected]: {}
|
||||||
|
|
||||||
|
|||||||
@@ -8,3 +8,16 @@ allowBuilds:
|
|||||||
'@embedded-postgres/windows-x64': true
|
'@embedded-postgres/windows-x64': true
|
||||||
'@embedded-postgres/linux-x64': true
|
'@embedded-postgres/linux-x64': true
|
||||||
unrs-resolver: false
|
unrs-resolver: false
|
||||||
|
|
||||||
|
# Transitive Luecken ab "high" (pnpm audit --prod, CI-Gate in
|
||||||
|
# .gitea/workflows/docker-build.yml). Gleiche Untergrenzen wie im Wabenhain-Repo.
|
||||||
|
overrides:
|
||||||
|
# mysql2 und deepmerge-ts kommen ueber die prisma-CLI (@prisma/config) herein,
|
||||||
|
# nicht ueber den Laufzeitpfad (die App nutzt @prisma/adapter-pg).
|
||||||
|
# deepmerge-ts ist ein Hauptversionssprung; "prisma generate" laeuft durch
|
||||||
|
# @prisma/config und prueft ihn bei jedem typecheck/build mit.
|
||||||
|
mysql2: '>=3.23.1'
|
||||||
|
deepmerge-ts: '>=8.0.0'
|
||||||
|
# Transitiv ueber next (next pinnt sharp sonst auf 0.35.4); reine Patch-Spruenge.
|
||||||
|
sharp: '>=0.35.5'
|
||||||
|
source-map-js: '>=1.2.2'
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Prüft im CI-Job die Grenzen des isolierten Runners (Label ci-isolated,
|
||||||
|
# Runner-Doku im VyCept-Repo, docs/BETRIEB.md 11b; Kopie von dort). Läuft als erster Schritt jedes Prüf-Jobs, damit jeder
|
||||||
|
# Runner bei jedem Lauf geprüft wird. Schlägt fehl, wenn der Job mehr darf als
|
||||||
|
# vorgesehen – oder wenn die Prüfung selbst nichts aussagen würde.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "Runner-Isolation verletzt: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
reachable() {
|
||||||
|
timeout 5 bash -c "exec 3<>/dev/tcp/$1/$2" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Namen mit Wiederholung auflösen: Das DNS des Job-Netzes antwortet unter Last
|
||||||
|
# gelegentlich erst beim zweiten Versuch.
|
||||||
|
resolve() {
|
||||||
|
local address=""
|
||||||
|
for _ in 1 2 3 4 5; do
|
||||||
|
address=$(getent hosts "$1" | awk '{ print $1; exit }' || true)
|
||||||
|
if [ -n "$address" ]; then echo "$address"; return 0; fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Kein Zugang zum Docker-Daemon über den Socket.
|
||||||
|
[ -z "${DOCKER_HOST:-}" ] || fail "DOCKER_HOST ist gesetzt ($DOCKER_HOST)"
|
||||||
|
for socket in /var/run/docker.sock /run/docker.sock; do
|
||||||
|
[ ! -e "$socket" ] || fail "Docker-Socket $socket ist sichtbar"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 2. Kein Zugang zur Docker-API des Daemons im Runner-Pod (Gateway des Job-Netzes):
|
||||||
|
# 2375 (ohne TLS) zu, 2376 nur mit Client-Zertifikat, das der Job nicht hat.
|
||||||
|
gateway_hex=$(awk '$2 == "00000000" { print $3; exit }' /proc/net/route)
|
||||||
|
[ -n "$gateway_hex" ] || fail "kein Standard-Gateway gefunden – Prüfung wäre wirkungslos"
|
||||||
|
gateway=$(printf '%d.%d.%d.%d' "0x${gateway_hex:6:2}" "0x${gateway_hex:4:2}" "0x${gateway_hex:2:2}" "0x${gateway_hex:0:2}")
|
||||||
|
if reachable "$gateway" 2375; then fail "Docker-API ohne TLS am Gateway $gateway:2375"; fi
|
||||||
|
if curl -sk --max-time 5 "https://$gateway:2376/version" >/dev/null 2>&1; then
|
||||||
|
fail "Docker-API am Gateway $gateway:2376 ohne Client-Zertifikat nutzbar"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Kein Clusternetz: Kubernetes-API (Service und Knoten) und fremde Namespaces.
|
||||||
|
for target in 10.43.0.1:443 10.50.1.51:6443 10.50.1.52:6443 10.50.1.53:6443; do
|
||||||
|
if reachable "${target%:*}" "${target##*:}"; then fail "$target ist erreichbar"; fi
|
||||||
|
done
|
||||||
|
resolve kubernetes.default.svc.cluster.local >/dev/null || fail "Clusternamen nicht auflösbar – Prüfung wäre wirkungslos"
|
||||||
|
# Fremder Namespace (VyCept-Datenbank) und eigener Namespace (Gitea-Datenbank).
|
||||||
|
for target in vycept-postgres.vycept.svc.cluster.local:5432 gitea-postgres.gitea.svc.cluster.local:5432; do
|
||||||
|
host=${target%:*}
|
||||||
|
address=$(resolve "$host") || fail "$host nicht auflösbar – Prüfung wäre wirkungslos"
|
||||||
|
if reachable "$address" "${target##*:}"; then fail "$host ($address) ist erreichbar"; fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 4. Internet bleibt erreichbar (npm, GitHub, Images).
|
||||||
|
reachable registry.npmjs.org 443 || fail "Internet (registry.npmjs.org) nicht erreichbar"
|
||||||
|
|
||||||
|
echo "Runner-Isolation in Ordnung (Gateway $gateway, kein Socket, kein Clusternetz)"
|
||||||
@@ -3,9 +3,9 @@ import { getAuth } from "@/lib/auth";
|
|||||||
import { withClientIp } from "@/lib/rate-limit";
|
import { withClientIp } from "@/lib/rate-limit";
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
return toNextJsHandler(getAuth()).GET(withClientIp(request));
|
return toNextJsHandler(getAuth()).GET(await withClientIp(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
return toNextJsHandler(getAuth()).POST(withClientIp(request));
|
return toNextJsHandler(getAuth()).POST(await withClientIp(request));
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import { headers } from "next/headers";
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import type { Actor } from "./audit";
|
import type { Actor } from "./audit";
|
||||||
import { getAuth } from "./auth";
|
import { getAuth } from "./auth";
|
||||||
|
import { headersWithClientIp } from "./rate-limit";
|
||||||
|
|
||||||
export type AdminUser = { id: string; email: string; name: string; twoFactorEnabled: boolean };
|
export type AdminUser = { id: string; email: string; name: string; twoFactorEnabled: boolean };
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ async function currentAdmin(): Promise<AdminUser | null> {
|
|||||||
// Sonst versucht "next build" sie vorab zu rendern und scheitert ohne
|
// Sonst versucht "next build" sie vorab zu rendern und scheitert ohne
|
||||||
// Secrets im Container (getAuth() braucht BETTER_AUTH_SECRET).
|
// Secrets im Container (getAuth() braucht BETTER_AUTH_SECRET).
|
||||||
const requestHeaders = await headers();
|
const requestHeaders = await headers();
|
||||||
const session = await getAuth().api.getSession({ headers: requestHeaders });
|
const session = await getAuth().api.getSession({ headers: headersWithClientIp(requestHeaders) });
|
||||||
if (!session) return null;
|
if (!session) return null;
|
||||||
const user = session.user as typeof session.user & { twoFactorEnabled?: boolean | null };
|
const user = session.user as typeof session.user & { twoFactorEnabled?: boolean | null };
|
||||||
return { id: user.id, email: user.email, name: user.name, twoFactorEnabled: Boolean(user.twoFactorEnabled) };
|
return { id: user.id, email: user.email, name: user.name, twoFactorEnabled: Boolean(user.twoFactorEnabled) };
|
||||||
|
|||||||
+12
-5
@@ -85,14 +85,21 @@ async function deliver(message: { id: string; toAddress: string; subject: string
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RECIPIENT_REJECTION_CODES = new Set([550, 551, 553, 556]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP-Antworten 5xx sind endgueltig (z. B. Empfaenger abgelehnt, Domain ohne
|
* Endgueltig ist nur eine Ablehnung des Empfaengers (nodemailer EENVELOPE, RCPT TO):
|
||||||
* Mailserver): erneutes Senden aendert nichts und liesse jeden Worker-Lauf
|
* 550/551/553/556 oder 5xx mit Status 5.1.x (Adresse/Domain falsch). Alles andere
|
||||||
* scheitern. 4xx und Verbindungsfehler bleiben wiederholbar.
|
* (Anmeldung EAUTH, Richtlinie/Blocklist EMESSAGE, 552, 4xx, Verbindung) liegt am
|
||||||
|
* Server oder Absender; die Mail bleibt PENDING und wird bis MAX_EMAIL_ATTEMPTS
|
||||||
|
* wiederholt, damit ein kaputtes SMTP-Secret keine Mails endgueltig verwirft.
|
||||||
*/
|
*/
|
||||||
export function isPermanentMailError(error: unknown): boolean {
|
export function isPermanentMailError(error: unknown): boolean {
|
||||||
const code = typeof error === "object" && error !== null ? (error as { responseCode?: unknown }).responseCode : undefined;
|
if (typeof error !== "object" || error === null) return false;
|
||||||
return typeof code === "number" && code >= 500 && code < 600;
|
const { code, responseCode, response } = error as { code?: unknown; responseCode?: unknown; response?: unknown };
|
||||||
|
if (code !== "EENVELOPE" || typeof responseCode !== "number") return false;
|
||||||
|
if (RECIPIENT_REJECTION_CODES.has(responseCode)) return true;
|
||||||
|
return responseCode >= 500 && responseCode < 600 && typeof response === "string" && /(^|\s)5\.1\.\d+(\s|$)/.test(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** SMTP-Fehlermeldungen nennen den Empfaenger; ins Log und in lastError kommt er nicht. */
|
/** SMTP-Fehlermeldungen nennen den Empfaenger; ins Log und in lastError kommt er nicht. */
|
||||||
|
|||||||
+41
-7
@@ -67,6 +67,26 @@ const CLOUDFLARE: [string, number, "ipv4" | "ipv6"][] = [
|
|||||||
const cloudflare = new BlockList();
|
const cloudflare = new BlockList();
|
||||||
for (const [net, prefix, family] of CLOUDFLARE) cloudflare.addSubnet(net, prefix, family);
|
for (const [net, prefix, family] of CLOUDFLARE) cloudflare.addSubnet(net, prefix, family);
|
||||||
|
|
||||||
|
/** Nicht oeffentliche Bereiche; 240/4 nutzt Cloudflare fuer "Pseudo IPv4". */
|
||||||
|
const NON_PUBLIC_V4 = ["0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.168.0.0/16", "240.0.0.0/4"];
|
||||||
|
const nonPublic = new BlockList();
|
||||||
|
for (const cidr of NON_PUBLIC_V4) nonPublic.addSubnet(cidr.split("/")[0]!, Number(cidr.split("/")[1]), "ipv4");
|
||||||
|
nonPublic.addAddress("255.255.255.255", "ipv4");
|
||||||
|
nonPublic.addAddress("::", "ipv6");
|
||||||
|
nonPublic.addAddress("::1", "ipv6");
|
||||||
|
nonPublic.addSubnet("fc00::", 7, "ipv6");
|
||||||
|
nonPublic.addSubnet("fe80::", 10, "ipv6");
|
||||||
|
|
||||||
|
function isPublicUnicast(ip: string): boolean {
|
||||||
|
if (isIP(ip) === 4) return !nonPublic.check(ip, "ipv4");
|
||||||
|
const groups = ipv6Groups(ip);
|
||||||
|
if (groups.slice(0, 5).every((group) => group === 0) && groups[5] === 0xffff) {
|
||||||
|
const [high = 0, low = 0] = groups.slice(6);
|
||||||
|
return !nonPublic.check([high >> 8, high & 0xff, low >> 8, low & 0xff].join("."), "ipv4");
|
||||||
|
}
|
||||||
|
return !nonPublic.check(ip, "ipv6");
|
||||||
|
}
|
||||||
|
|
||||||
function validIp(value: string | null | undefined): string | null {
|
function validIp(value: string | null | undefined): string | null {
|
||||||
const ip = value?.trim();
|
const ip = value?.trim();
|
||||||
return ip && isIP(ip) ? ip : null;
|
return ip && isIP(ip) ? ip : null;
|
||||||
@@ -82,10 +102,11 @@ function validIp(value: string | null | undefined): string | null {
|
|||||||
*/
|
*/
|
||||||
export function clientIp(headers: Headers): string {
|
export function clientIp(headers: Headers): string {
|
||||||
const forwarded = headers.get("x-forwarded-for");
|
const forwarded = headers.get("x-forwarded-for");
|
||||||
const peer = forwarded === null ? validIp(headers.get("x-real-ip")) : validIp(forwarded.split(",").at(-1));
|
const realIp = process.env.NODE_ENV === "production" ? null : validIp(headers.get("x-real-ip"));
|
||||||
|
const peer = forwarded === null ? realIp : validIp(forwarded.split(",").at(-1));
|
||||||
if (!peer) return "unknown";
|
if (!peer) return "unknown";
|
||||||
const viaCloudflare = validIp(headers.get("cf-connecting-ip"));
|
const viaCloudflare = validIp(headers.get("cf-connecting-ip"));
|
||||||
return viaCloudflare && cloudflare.check(peer, isIP(peer) === 6 ? "ipv6" : "ipv4") ? viaCloudflare : peer;
|
return viaCloudflare && isPublicUnicast(viaCloudflare) && cloudflare.check(peer, isIP(peer) === 6 ? "ipv6" : "ipv4") ? viaCloudflare : peer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Die acht 16-Bit-Gruppen einer gueltigen IPv6-Adresse; eine IPv4-Endung (::ffff:a.b.c.d) wird umgerechnet. */
|
/** Die acht 16-Bit-Gruppen einer gueltigen IPv6-Adresse; eine IPv4-Endung (::ffff:a.b.c.d) wird umgerechnet. */
|
||||||
@@ -119,9 +140,22 @@ export function rateLimitSubject(ip: string): string {
|
|||||||
return `${groups.slice(0, 4).map((group) => group.toString(16)).join(":")}::/64`;
|
return `${groups.slice(0, 4).map((group) => group.toString(16)).join(":")}::/64`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Kopie der Anfrage mit CLIENT_IP_HEADER aus clientIp(); ein mitgeschickter Wert wird ersetzt. */
|
/** Kopie der Header mit CLIENT_IP_HEADER aus clientIp(); ein mitgeschickter Wert wird ersetzt. */
|
||||||
export function withClientIp(request: Request): Request {
|
export function headersWithClientIp(source: Headers): Headers {
|
||||||
const headers = new Headers(request.headers);
|
const headers = new Headers(source);
|
||||||
headers.set(CLIENT_IP_HEADER, clientIp(request.headers));
|
headers.set(CLIENT_IP_HEADER, clientIp(source));
|
||||||
return new Request(request, { headers });
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kopie der Anfrage mit CLIENT_IP_HEADER aus clientIp(); ein mitgeschickter Wert wird ersetzt.
|
||||||
|
*
|
||||||
|
* Bewusst aus URL, Methode und Body neu gebaut statt `new Request(request, …)`:
|
||||||
|
* Next reicht Route-Handlern einen Proxy um NextRequest, und undici 7 (Node 24)
|
||||||
|
* prueft private Felder (#state), die ein Proxy nicht hat ("Cannot read private
|
||||||
|
* member #state"). Unter Node 22 (undici 6, Symbole) fiel das nicht auf.
|
||||||
|
*/
|
||||||
|
export async function withClientIp(request: Request): Promise<Request> {
|
||||||
|
const body = request.method === "GET" || request.method === "HEAD" ? undefined : await request.arrayBuffer();
|
||||||
|
return new Request(request.url, { method: request.method, headers: headersWithClientIp(request.headers), body, signal: request.signal });
|
||||||
}
|
}
|
||||||
+70
-6
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||||
import { CLIENT_IP_HEADER, clientIp, rateLimitSubject, withClientIp } from "@/lib/rate-limit";
|
import { CLIENT_IP_HEADER, clientIp, headersWithClientIp, rateLimitSubject, withClientIp } from "@/lib/rate-limit";
|
||||||
|
|
||||||
const h = (values: Record<string, string>) => new Headers(values);
|
const h = (values: Record<string, string>) => new Headers(values);
|
||||||
|
|
||||||
@@ -28,17 +28,81 @@ describe("clientIp hinter Traefik", () => {
|
|||||||
expect(rateLimitSubject("198.51.100.9")).toBe("198.51.100.9");
|
expect(rateLimitSubject("198.51.100.9")).toBe("198.51.100.9");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("better-auth bekommt eine einzelne gültige IP (es normalisiert IPv6 selbst auf /64)", () => {
|
test("better-auth bekommt eine einzelne gültige IP (es normalisiert IPv6 selbst auf /64)", async () => {
|
||||||
const request = new Request("https://example.org/api/auth/sign-in/email", { headers: { "x-forwarded-for": "2001:db8:1:2:aaaa::1" } });
|
const request = new Request("https://example.org/api/auth/sign-in/email", { headers: { "x-forwarded-for": "2001:db8:1:2:aaaa::1" } });
|
||||||
expect(withClientIp(request).headers.get(CLIENT_IP_HEADER)).toBe("2001:db8:1:2:aaaa::1");
|
expect((await withClientIp(request)).headers.get(CLIENT_IP_HEADER)).toBe("2001:db8:1:2:aaaa::1");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("withClientIp setzt den Header für better-auth und überschreibt einen mitgeschickten Wert", () => {
|
test("withClientIp setzt den Header für better-auth und überschreibt einen mitgeschickten Wert", async () => {
|
||||||
const request = new Request("https://example.org/api/auth/sign-in/email", {
|
const request = new Request("https://example.org/api/auth/sign-in/email", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "x-forwarded-for": "1.2.3.4, 198.51.100.9", [CLIENT_IP_HEADER]: "1.2.3.4" },
|
headers: { "x-forwarded-for": "1.2.3.4, 198.51.100.9", [CLIENT_IP_HEADER]: "1.2.3.4" },
|
||||||
body: "{}",
|
body: "{}",
|
||||||
});
|
});
|
||||||
expect(withClientIp(request).headers.get(CLIENT_IP_HEADER)).toBe("198.51.100.9");
|
expect((await withClientIp(request)).headers.get(CLIENT_IP_HEADER)).toBe("198.51.100.9");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withClientIp verträgt Nexts Proxy um NextRequest (Node 24, undici 7) und behält Methode und Body", async () => {
|
||||||
|
// Next 16 reicht Route-Handlern einen Proxy, dessen Methoden an das Original gebunden sind.
|
||||||
|
// undici 7 prüft private Felder (#state); ein Proxy besteht diese Prüfung nicht.
|
||||||
|
const original = new Request("https://example.org/api/auth/sign-in/email", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "x-forwarded-for": "198.51.100.9", "content-type": "application/json" },
|
||||||
|
body: '{"email":"[email protected]"}',
|
||||||
|
});
|
||||||
|
const wieNext = new Proxy(original, {
|
||||||
|
get(target, key) {
|
||||||
|
const wert = Reflect.get(target, key, target);
|
||||||
|
return typeof wert === "function" ? wert.bind(target) : wert;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const kopie = await withClientIp(wieNext);
|
||||||
|
expect(kopie.method).toBe("POST");
|
||||||
|
expect(kopie.headers.get(CLIENT_IP_HEADER)).toBe("198.51.100.9");
|
||||||
|
expect(await kopie.text()).toBe('{"email":"[email protected]"}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("CF-Connecting-IP nur als öffentliche Unicast-Adresse", () => {
|
||||||
|
const edge = "172.70.1.2";
|
||||||
|
const viaEdge = (cf: string) => clientIp(h({ "x-forwarded-for": `1.2.3.4, ${edge}`, "cf-connecting-ip": cf }));
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
"0.1.2.3", "10.0.0.5", "100.64.0.1", "100.127.255.255", "127.0.0.1", "169.254.1.1", "172.16.0.1", "172.31.255.255",
|
||||||
|
"192.168.1.1", "240.0.0.1", "255.255.255.255", "::", "::1", "fc00::1", "fd12::1", "fe80::1", "::ffff:10.0.0.1", "::ffff:192.168.0.1",
|
||||||
|
])("%s wird verworfen, die Gegenstelle gilt", (cf) => {
|
||||||
|
expect(viaEdge(cf)).toBe(edge);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(["203.0.113.7", "100.63.255.255", "172.32.0.1", "2001:db8::1", "::ffff:203.0.113.7"])("%s wird übernommen", (cf) => {
|
||||||
|
expect(viaEdge(cf)).toBe(cf);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("X-Real-IP in Produktion", () => {
|
||||||
|
afterEach(() => vi.unstubAllEnvs());
|
||||||
|
|
||||||
|
test("ohne X-Forwarded-For ergibt X-Real-IP in Produktion „unknown“", () => {
|
||||||
|
vi.stubEnv("NODE_ENV", "production");
|
||||||
|
expect(clientIp(h({ "x-real-ip": "198.51.100.9" }))).toBe("unknown");
|
||||||
|
expect(clientIp(h({ "x-forwarded-for": "198.51.100.9" }))).toBe("198.51.100.9");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("außerhalb von Produktion gilt X-Real-IP weiter", () => {
|
||||||
|
vi.stubEnv("NODE_ENV", "development");
|
||||||
|
expect(clientIp(h({ "x-real-ip": "198.51.100.9" }))).toBe("198.51.100.9");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("headersWithClientIp", () => {
|
||||||
|
test("überschreibt einen mitgeschickten Wert und lässt das Original unverändert", () => {
|
||||||
|
const original = h({ "x-forwarded-for": "1.2.3.4, 198.51.100.9", [CLIENT_IP_HEADER]: "1.2.3.4" });
|
||||||
|
const result = headersWithClientIp(original);
|
||||||
|
expect(result.get(CLIENT_IP_HEADER)).toBe("198.51.100.9");
|
||||||
|
expect(original.get(CLIENT_IP_HEADER)).toBe("1.2.3.4");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ohne ermittelbare IP steht „unknown“ im Header, nie der Clientwert", () => {
|
||||||
|
expect(headersWithClientIp(h({ [CLIENT_IP_HEADER]: "1.2.3.4" })).get(CLIENT_IP_HEADER)).toBe("unknown");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -13,8 +13,8 @@ vi.mock("nodemailer", async (importOriginal) => {
|
|||||||
return { ...actual, default: { ...actual.default, createTransport } };
|
return { ...actual, default: { ...actual.default, createTransport } };
|
||||||
});
|
});
|
||||||
|
|
||||||
function smtpError(message: string, responseCode?: number) {
|
function smtpError(message: string, responseCode?: number, code = "EENVELOPE") {
|
||||||
return Object.assign(new Error(message), { code: "EENVELOPE", responseCode });
|
return Object.assign(new Error(message), { code, responseCode });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function queueMail(to = "[email protected]") {
|
async function queueMail(to = "[email protected]") {
|
||||||
@@ -79,4 +79,38 @@ describe("Postausgang: Fehler beim SMTP-Versand", () => {
|
|||||||
expect(await db.emailMessage.findUniqueOrThrow({ where: { id } })).toMatchObject({ status: "PENDING", attempts: 1 });
|
expect(await db.emailMessage.findUniqueOrThrow({ where: { id } })).toMatchObject({ status: "PENDING", attempts: 1 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Server- und Absenderfehler (Anmeldung, Richtlinie) bleiben PENDING statt FAILED", async () => {
|
||||||
|
const auth = await queueMail("[email protected]");
|
||||||
|
const policy = await queueMail("[email protected]");
|
||||||
|
sendMail
|
||||||
|
.mockRejectedValueOnce(smtpError("535 5.7.8 Authentication failed", 535, "EAUTH"))
|
||||||
|
.mockRejectedValueOnce(smtpError("554 5.7.1 Message rejected as spam", 554, "EMESSAGE"));
|
||||||
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
||||||
|
|
||||||
|
expect(await flushEmails(db)).toEqual({ sent: 0, failed: 2 });
|
||||||
|
for (const id of [auth.id, policy.id]) {
|
||||||
|
expect(await db.emailMessage.findUniqueOrThrow({ where: { id } })).toMatchObject({ status: "PENDING", attempts: 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("EENVELOPE mit 550/551/553/556 oder 5.1.x ist endgueltig, 552 und 4xx nicht", async () => {
|
||||||
|
const cases: [number, string, boolean][] = [
|
||||||
|
[550, "550 5.1.1 User unknown", true],
|
||||||
|
[551, "551 User not local", true],
|
||||||
|
[553, "553 Mailbox name not allowed", true],
|
||||||
|
[554, "554 5.1.2 Bad destination system address", true],
|
||||||
|
[552, "552 Mailbox full", false],
|
||||||
|
[451, "451 4.1.1 Try later", false],
|
||||||
|
];
|
||||||
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
||||||
|
for (const [index, [code, response, permanent]] of cases.entries()) {
|
||||||
|
await resetDb();
|
||||||
|
const mail = await queueMail(`r${index}@example.org`);
|
||||||
|
sendMail.mockRejectedValueOnce(Object.assign(smtpError(response, code), { response }));
|
||||||
|
await flushEmails(db);
|
||||||
|
const after = await db.emailMessage.findUniqueOrThrow({ where: { id: mail.id } });
|
||||||
|
expect(after.status, response).toBe(permanent ? "FAILED" : "PENDING");
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user