feat: Add initial project setup including superadmin seeding, a Docker entrypoint for the admin app, and a comprehensive README.

This commit is contained in:
Timo Knuth 2026-02-27 21:33:40 +01:00
parent 244da5e69a
commit 8999cdbab3
3 changed files with 210 additions and 179 deletions

View File

@ -1,180 +1,154 @@
# InnungsApp # InnungsApp
Die digitale Plattform für Innungen — News, Mitgliederverzeichnis, Termine und Lehrlingsbörse. Digitale Plattform fuer Innungen mit Admin-Dashboard (Next.js) und Mobile App (Expo).
## Stack ## Stack
| Schicht | Technologie | | Layer | Technology |
|---|---| |---|---|
| **Monorepo** | pnpm Workspaces + Turborepo | | Monorepo | pnpm Workspaces + Turborepo |
| **Mobile App** | Expo (React Native) + Expo Router | | Admin Dashboard | Next.js 15 (App Router) |
| **Admin Dashboard** | Next.js 15 (App Router) | | Mobile App | Expo + React Native |
| **API** | tRPC v11 | | API | tRPC v11 |
| **Auth** | better-auth (Magic Links) | | Auth | better-auth (magic links + credential login) |
| **Datenbank** | PostgreSQL + Prisma ORM | | Database | SQLite + Prisma ORM |
| **Styling Mobile** | NativeWind v4 (Tailwind CSS) | | Styling | Tailwind CSS (admin), NativeWind (mobile) |
| **Styling Admin** | Tailwind CSS |
| **State Management** | Zustand (Mobile) + React Query (beide Apps) |
## Projekt-Struktur ## Projektstruktur
``` ```text
innungsapp/ innungsapp/
├── apps/ |-- apps/
│ ├── mobile/ # Expo React Native App (iOS + Android) | |-- admin/
│ └── admin/ # Next.js Admin Dashboard | `-- mobile/
├── packages/ |-- packages/
│ └── shared/ # TypeScript-Typen + Prisma Client | `-- shared/
└── ... | `-- prisma/
|-- docker-compose.yml
`-- README.md
``` ```
## Setup ## Local Setup
### Voraussetzungen ### Voraussetzungen
- Node.js >= 20 - Node.js >= 20
- pnpm >= 9 - pnpm >= 9
- PostgreSQL-Datenbank - SMTP-Zugang (fuer Einladungen und Magic Links)
- SMTP-Server (für Magic Links)
### 1. Abhängigkeiten installieren ### 1. Abhaengigkeiten installieren
```bash ```bash
pnpm install pnpm install
``` ```
### 2. Umgebungsvariablen ### 2. Umgebungsvariablen setzen (Admin lokal)
```bash ```bash
cp .env.example apps/admin/.env.local cp .env.example apps/admin/.env.local
# .env.local befüllen (DATABASE_URL, BETTER_AUTH_SECRET, SMTP_*)
``` ```
### 3. Datenbank einrichten Danach `apps/admin/.env.local` anpassen (mindestens `BETTER_AUTH_SECRET`, SMTP-Werte).
### 3. DB vorbereiten (lokal)
```bash ```bash
# Prisma Client generieren
pnpm db:generate pnpm db:generate
pnpm db:push
```
# Migrationen anwenden Optional Demo-Daten:
pnpm db:migrate
# Demo-Daten einspielen (optional) ```bash
pnpm db:seed pnpm db:seed
``` ```
### 4. Entwicklung starten ### 4. Entwicklung starten
```bash ```bash
# Admin Dashboard (http://localhost:3000)
pnpm --filter @innungsapp/admin dev pnpm --filter @innungsapp/admin dev
# Mobile App (Expo DevTools)
pnpm --filter @innungsapp/mobile dev pnpm --filter @innungsapp/mobile dev
``` ```
Oder alles parallel: Oder parallel:
```bash ```bash
pnpm dev pnpm dev
``` ```
## Datenbank-Schema ## Production Deployment (Docker, Admin)
Das Schema befindet sich in `packages/shared/prisma/schema.prisma`. Dieser Abschnitt ist der verbindliche Weg fuer den Productive-Server.
Wichtige Tabellen: ### Voraussetzungen
- `organizations` — Innungen (Multi-Tenancy)
- `members` — Mitglieder (verknüpft mit Auth-User nach Einladung)
- `user_roles` — Berechtigungen (admin | member)
- `news`, `news_reads`, `news_attachments` — News-System
- `termine`, `termin_anmeldungen` — Terminverwaltung
- `stellen` — Lehrlingsbörse (öffentlich lesbar)
## Auth-Flow - Linux Server mit Docker + Docker Compose
- DNS-Eintrag auf den Server
- SMTP-Zugangsdaten
- Reverse Proxy (z. B. Nginx) fuer HTTPS
1. **Admin einrichten:** Seed-Daten oder manuell in der DB ### 1. Repository klonen
2. **Mitglied einladen:** Admin erstellt Mitglied → "Einladung senden" → Magic Link per E-Mail
3. **Mitglied loggt ein:** Magic Link → Session → App-Zugang
## API (tRPC)
Alle API-Endpunkte sind typsicher über tRPC definiert:
- `organizations.*` — Org-Einstellungen, Stats, AVV
- `members.*` — CRUD, Einladungen
- `news.*` — CRUD, Lesestatus, Push-Benachrichtigungen
- `termine.*` — CRUD, Anmeldungen
- `stellen.*` — Public + Auth-geschützte Endpunkte
## Deployment
### Admin — Docker (empfohlen für Self-Hosting)
**Voraussetzungen:** Docker + Docker Compose auf dem Server installiert.
#### Schritt 1: Repository klonen
```bash ```bash
git clone <repo-url> git clone <repo-url>
cd innungsapp cd innungsapp
``` ```
#### Schritt 2: Umgebungsvariablen anlegen ### 2. Production-Env anlegen
```bash ```bash
cp .env.production.example .env cp .env.production.example .env
``` ```
Dann `.env` öffnen und **alle Werte** befüllen: Pflichtwerte in `.env`:
| Variable | Beschreibung | - `BETTER_AUTH_SECRET` (mindestens 32 Zeichen)
|---|---| - `BETTER_AUTH_URL` (z. B. `https://app.deine-innung.de`)
| `BETTER_AUTH_SECRET` | Zufälliger String (min. 32 Zeichen) — z.B. `openssl rand -hex 32` | - `NEXT_PUBLIC_APP_URL` (gleich wie `BETTER_AUTH_URL`)
| `BETTER_AUTH_URL` | Öffentliche URL der App, z.B. `https://app.deine-innung.de` | - `EMAIL_FROM`
| `NEXT_PUBLIC_APP_URL` | Gleicher Wert wie `BETTER_AUTH_URL` | - `SMTP_HOST`
| `EMAIL_FROM` | Absender-Adresse für Magic Links | - `SMTP_PORT`
| `SMTP_HOST` | SMTP-Server-Adresse | - `SMTP_SECURE`
| `SMTP_PORT` | Meistens `587` (STARTTLS) oder `465` (SSL) | - `SMTP_USER`
| `SMTP_USER` | SMTP-Benutzername | - `SMTP_PASS`
| `SMTP_PASS` | SMTP-Passwort |
#### Schritt 3: Container bauen und starten ### 3. Container bauen und starten
```bash ```bash
docker compose up -d --build docker compose up -d --build
``` ```
Der Build dauert beim ersten Mal ~23 Minuten. Danach läuft die App auf **Port 3000**. Hinweis zum DB-Start:
- Wenn Prisma-Migrationen vorhanden sind, wird `prisma migrate deploy` ausgefuehrt.
- Wenn keine Migrationen vorhanden sind, wird einmalig `prisma db push` ausgefuehrt.
### 4. Healthcheck und Logs pruefen
Logs prüfen:
```bash ```bash
docker compose logs -f admin docker compose logs -f admin
curl -fsS http://localhost:3000/api/health
``` ```
#### Schritt 4: Superadmin anlegen (nur beim ersten Start) Erwartet: JSON mit `status: "ok"`.
### 5. Superadmin anlegen (nur beim ersten Start)
```bash ```bash
docker compose exec admin node -e " docker compose exec -w /app admin node packages/shared/prisma/seed-superadmin.js
const { PrismaClient } = require('@prisma/client');
const { scryptSync, randomBytes } = require('crypto');
const prisma = new PrismaClient();
// Superadmin wird via seed-superadmin.ts angelegt
"
``` ```
Einfacher: Den Seed direkt ausführen: Default Login:
```bash - E-Mail: `superadmin@innungsapp.de`
docker compose exec -w /app admin \ - Passwort: `demo1234`
node packages/shared/prisma/seed-superadmin.js
```
> Standard-Login nach Seed: `superadmin@innungsapp.de` / `demo1234` Passwort direkt nach dem ersten Login aendern.
> **Passwort sofort in den Einstellungen ändern!**
#### Schritt 5: Reverse Proxy (HTTPS) ### 6. HTTPS (Reverse Proxy)
Nginx-Beispielkonfiguration für `app.deine-innung.de`: Nginx sollte auf `localhost:3000` weiterleiten und TLS terminieren.
Beispiel:
```nginx ```nginx
server { server {
@ -190,70 +164,56 @@ server {
ssl_certificate /etc/letsencrypt/live/app.deine-innung.de/fullchain.pem; ssl_certificate /etc/letsencrypt/live/app.deine-innung.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.deine-innung.de/privkey.pem; ssl_certificate_key /etc/letsencrypt/live/app.deine-innung.de/privkey.pem;
client_max_body_size 20M;
location / { location / {
proxy_pass http://localhost:3000; proxy_pass http://localhost:3000;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
} }
} }
``` ```
SSL-Zertifikat mit Certbot: ### 7. Updates einspielen
```bash
certbot --nginx -d app.deine-innung.de
```
#### Updates einspielen
```bash ```bash
git pull git pull
docker compose up -d --build docker compose up -d --build
```
Datenbank und Uploads bleiben dabei erhalten (Docker Volumes).
#### Häufige Befehle
```bash
# Status prüfen
docker compose ps
# Logs ansehen
docker compose logs -f admin docker compose logs -f admin
# Container neustarten
docker compose restart admin
# In Container einloggen
docker compose exec admin sh
# App stoppen
docker compose down
# App stoppen + Daten löschen (Vorsicht!)
docker compose down -v
``` ```
--- ### 8. Backup und Restore (Docker Volumes)
### Admin — Vercel (Alternative) Vorher die exakten Volumenamen pruefen:
```bash ```bash
# Umgebungsvariablen in Vercel setzen: docker volume ls | grep db_data
# DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL, SMTP_* docker volume ls | grep uploads_data
vercel --cwd apps/admin
``` ```
### Mobile (EAS Build) Backup:
```bash
mkdir -p backups
docker run --rm \
-v innungsapp_db_data:/volume \
-v "$(pwd)/backups:/backup" \
alpine sh -c "tar czf /backup/db_data_$(date +%F_%H%M).tar.gz -C /volume ."
```
Restore (nur bei gestoppter App):
```bash
docker compose down
docker run --rm \
-v innungsapp_db_data:/volume \
-v "$(pwd)/backups:/backup" \
alpine sh -c "rm -rf /volume/* && tar xzf /backup/<backup-file>.tar.gz -C /volume"
docker compose up -d
```
## Mobile Release (EAS)
```bash ```bash
cd apps/mobile cd apps/mobile
@ -261,33 +221,32 @@ eas build --platform all --profile production
eas submit --platform all eas submit --platform all
``` ```
## DSGVO / AVV Wichtig:
- AVV-Akzeptanz in Admin → Einstellungen (Pflichtfeld vor Go-Live) - In `apps/mobile/eas.json` sind Submit-Placeholders vorhanden und muessen ersetzt werden.
- Alle personenbezogenen Daten in EU-Region (Datenbankserver in Deutschland empfohlen) - Fuer Production darf keine API-URL auf `localhost` zeigen.
- Keine Daten an Dritte außer Expo Push API (anonymisierte Token)
## Roadmap ## Troubleshooting
Siehe `innung-app-mvp.md` für die vollständige Roadmap. ### `migrate deploy` oder `db push` fehlschlaegt
## Apps starten (Schnellstart) - `DATABASE_URL` pruefen
- Rechte auf `/app/data` pruefen
- Logs: `docker compose logs -f admin`
Um die Anwendungen lokal zu starten, öffne ein Terminal im Hauptverzeichnis (`innungsapp/`) und nutze folgende Befehle: ### Healthcheck liefert Fehler
**Admin Dashboard starten:** - Containerstatus: `docker compose ps`
```bash - App-Logs lesen
pnpm --filter @innungsapp/admin dev - Reverse Proxy testweise umgehen und direkt `localhost:3000` pruefen
```
Das Dashboard ist im Browser unter [http://localhost:3000](http://localhost:3000) erreichbar.
**Mobile App starten:** ### Login funktioniert nicht nach Seed
```bash
pnpm --filter @innungsapp/mobile dev
```
Dies startet den Expo-Server. Scanne den QR-Code mit der **Expo Go App** auf deinem Smartphone oder drücke `a` (für den Android Emulator) bzw. `i` (für den iOS Simulator) im Terminal.
**Beides gleichzeitig starten:** - Seed-Command erneut ausfuehren
```bash - In DB pruefen, ob `user` und `account` Eintraege fuer `superadmin@innungsapp.de` existieren
pnpm dev
``` ## Weiterfuehrende Doku
- Produkt-Roadmap: `../ROADMAP.md`
- Architektur: `../ARCHITECTURE.md`
- API Design: `../API_DESIGN.md`

View File

@ -1,11 +1,19 @@
#!/bin/sh #!/bin/sh
set -e set -e
# Run Prisma migrations on startup # Keep DATABASE_URL consistent for every Prisma command
echo "Running database migrations..." export DATABASE_URL="${DATABASE_URL:-file:/app/data/prod.db}"
DATABASE_URL="${DATABASE_URL:-file:/app/data/prod.db}" \ MIGRATIONS_DIR="./packages/shared/prisma/migrations"
node_modules/.bin/prisma migrate deploy \
--schema=./packages/shared/prisma/schema.prisma # Prefer migration-based deploys. Fall back to db push when no migrations exist yet.
set -- "$MIGRATIONS_DIR"/*/migration.sql
if [ -f "$1" ]; then
echo "Applying Prisma migrations..."
node_modules/.bin/prisma migrate deploy --schema=./packages/shared/prisma/schema.prisma
else
echo "No Prisma migrations found. Syncing schema with db push..."
node_modules/.bin/prisma db push --skip-generate --schema=./packages/shared/prisma/schema.prisma
fi
echo "Starting Next.js server..." echo "Starting Next.js server..."
exec node apps/admin/server.js exec node apps/admin/server.js

View File

@ -0,0 +1,64 @@
const { PrismaClient } = require('@prisma/client')
const { randomBytes, scrypt } = require('crypto')
const { promisify } = require('util')
const prisma = new PrismaClient()
const scryptAsync = promisify(scrypt)
async function hashPassword(password) {
const salt = randomBytes(16).toString('hex')
const key = await scryptAsync(password.normalize('NFKC'), salt, 64, {
N: 16384,
r: 16,
p: 1,
maxmem: 128 * 16384 * 16 * 2,
})
return `${salt}:${key.toString('hex')}`
}
async function main() {
const email = 'superadmin@innungsapp.de'
const password = 'demo1234'
const userId = 'superadmin-user-id'
const accountId = 'superadmin-account-id'
console.log('Seeding superadmin...')
const hash = await hashPassword(password)
const user = await prisma.user.upsert({
where: { email },
update: {
name: 'Super Admin',
emailVerified: true,
},
create: {
id: userId,
name: 'Super Admin',
email,
emailVerified: true,
},
})
await prisma.account.upsert({
where: { id: accountId },
update: { password: hash },
create: {
id: accountId,
accountId: user.id,
providerId: 'credential',
userId: user.id,
password: hash,
},
})
console.log(`Done. Login: ${email} / ${password}`)
}
main()
.catch((error) => {
console.error(error)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})