342 lines
12 KiB
Markdown
342 lines
12 KiB
Markdown
# InnungsApp — Technische Architektur
|
||
|
||
> **Version:** 1.0 | **Stand:** Februar 2026
|
||
|
||
---
|
||
|
||
## 1. Überblick
|
||
|
||
InnungsApp besteht aus drei Hauptkomponenten:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ CLIENTS │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||
│ │ Mobile App │ │ Admin Web App │ │
|
||
│ │ (React Native) │ │ (Next.js) │ │
|
||
│ │ iOS + Android │ │ Browser │ │
|
||
│ └────────┬────────┘ └────────┬────────┘ │
|
||
└───────────┼────────────────────┼──────────────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ SUPABASE │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
|
||
│ │ Auth │ │ Postgres │ │ Storage │ │ Realtime │ │
|
||
│ │ Magic │ │ + RLS │ │ PDFs │ │ Push + │ │
|
||
│ │ Link │ │ Database │ │ Images │ │ Events │ │
|
||
│ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ EXTERNE DIENSTE │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
|
||
│ │ Expo │ │ Resend │ │ PostHog │ │ Mux │ │
|
||
│ │ Push │ │ E-Mail │ │ Analytics│ │ Video │ │
|
||
│ │ (FCM/APNs│ │ Transact.│ │ │ │ (Q2) │ │
|
||
│ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Mobile App (React Native + Expo)
|
||
|
||
### Tech Stack
|
||
|
||
| Schicht | Technologie | Begründung |
|
||
|---|---|---|
|
||
| Framework | React Native 0.74 + Expo SDK 51 | Eine Codebasis iOS + Android |
|
||
| Navigation | Expo Router v3 (file-based) | Typ-sicher, einfach wartbar |
|
||
| State Management | Zustand | Leichtgewichtig, kein Redux-Overhead |
|
||
| Data Fetching | TanStack Query v5 | Caching, Background Refetch, Optimistic Updates |
|
||
| UI-Komponenten | Custom + NativeWind (Tailwind on Native) | Konsistentes Design, schnelle Entwicklung |
|
||
| Push Notifications | Expo Notifications + FCM/APNs | Out-of-the-box mit Expo |
|
||
| Auth | Supabase Auth Client | Magic Link Flow |
|
||
| Type Safety | TypeScript (strict mode) | Pflicht für Produktionscode |
|
||
|
||
### Ordnerstruktur
|
||
|
||
```
|
||
apps/mobile/
|
||
├── app/ # Expo Router — File-based Navigation
|
||
│ ├── (auth)/
|
||
│ │ ├── login.tsx # Magic Link Login Screen
|
||
│ │ └── verify.tsx # Token Verification
|
||
│ ├── (tabs)/
|
||
│ │ ├── _layout.tsx # Tab Bar Layout
|
||
│ │ ├── index.tsx # Dashboard / Home Feed
|
||
│ │ ├── members.tsx # Mitgliederverzeichnis
|
||
│ │ ├── news.tsx # Mitteilungen Feed
|
||
│ │ ├── termine.tsx # Terminkalender
|
||
│ │ └── stellen.tsx # Lehrlingsbörse
|
||
│ ├── member/[id].tsx # Mitglied Detailansicht
|
||
│ ├── news/[id].tsx # Beitrag Detailansicht
|
||
│ ├── termin/[id].tsx # Termin Detailansicht
|
||
│ └── _layout.tsx # Root Layout (Auth Guard)
|
||
├── components/
|
||
│ ├── ui/ # Atomare UI-Komponenten
|
||
│ │ ├── Button.tsx
|
||
│ │ ├── Card.tsx
|
||
│ │ ├── Badge.tsx
|
||
│ │ ├── Avatar.tsx
|
||
│ │ └── Input.tsx
|
||
│ ├── members/ # Feature-spezifische Komponenten
|
||
│ ├── news/
|
||
│ ├── termine/
|
||
│ └── stellen/
|
||
├── hooks/
|
||
│ ├── useAuth.ts
|
||
│ ├── useMembers.ts
|
||
│ ├── useNews.ts
|
||
│ └── usePushNotifications.ts
|
||
├── lib/
|
||
│ ├── supabase.ts # Supabase Client Singleton
|
||
│ ├── queryClient.ts # TanStack Query Client
|
||
│ └── notifications.ts # Push Token Registration
|
||
├── store/
|
||
│ └── auth.ts # Zustand Auth Store
|
||
├── types/
|
||
│ └── database.ts # Generierte Supabase Types
|
||
└── constants/
|
||
├── colors.ts # Design Tokens
|
||
└── config.ts # Env-Variablen
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Admin Web App (Next.js)
|
||
|
||
### Tech Stack
|
||
|
||
| Schicht | Technologie |
|
||
|---|---|
|
||
| Framework | Next.js 14 (App Router) |
|
||
| Styling | Tailwind CSS + shadcn/ui |
|
||
| Auth | Supabase Auth (SSR) |
|
||
| Data Fetching | Server Components + TanStack Query (Client) |
|
||
| Tables | TanStack Table v8 |
|
||
| Forms | React Hook Form + Zod |
|
||
| Charts | Recharts |
|
||
| Deployment | Vercel |
|
||
|
||
### Ordnerstruktur
|
||
|
||
```
|
||
apps/admin/
|
||
├── app/
|
||
│ ├── (auth)/
|
||
│ │ └── login/page.tsx
|
||
│ ├── (dashboard)/
|
||
│ │ ├── layout.tsx # Sidebar Layout
|
||
│ │ ├── page.tsx # Overview Dashboard
|
||
│ │ ├── members/
|
||
│ │ │ ├── page.tsx # Mitgliederliste
|
||
│ │ │ ├── new/page.tsx # Mitglied anlegen
|
||
│ │ │ └── [id]/page.tsx # Mitglied bearbeiten
|
||
│ │ ├── news/
|
||
│ │ │ ├── page.tsx
|
||
│ │ │ └── new/page.tsx
|
||
│ │ ├── termine/
|
||
│ │ ├── stellen/
|
||
│ │ └── settings/
|
||
│ └── layout.tsx
|
||
├── components/
|
||
│ ├── ui/ # shadcn/ui Komponenten
|
||
│ ├── data-table/
|
||
│ └── forms/
|
||
└── lib/
|
||
├── supabase-server.ts # Supabase SSR Client
|
||
└── actions.ts # Server Actions
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Backend: Supabase
|
||
|
||
### Warum Supabase?
|
||
|
||
- **Kein eigener API-Server** nötig für MVP → spart 4–6 Wochen Entwicklung
|
||
- **PostgreSQL** mit vollem SQL-Zugriff → keine NoSQL-Kompromisse
|
||
- **Row Level Security** → Multi-Tenancy ohne eigene Middleware
|
||
- **Realtime** → Live-Updates ohne WebSocket-Implementierung
|
||
- **Storage** → S3-kompatibel, CDN included
|
||
- **Auth** → Magic Link, Sessions, JWT out-of-the-box
|
||
- **EU-Region Frankfurt** → DSGVO-konform
|
||
|
||
### Supabase Services genutzt
|
||
|
||
| Service | Verwendung |
|
||
|---|---|
|
||
| Auth | Magic Link Login, Session-Management, JWT |
|
||
| Database | PostgreSQL, alle Tabellen |
|
||
| Row Level Security | Multi-Tenancy, Datenisolation |
|
||
| Storage | PDF-Anhänge, Profilbilder, Logos |
|
||
| Realtime | Live-Updates (News Feed, Teilnehmerlisten) |
|
||
| Edge Functions | Komplexe Businesslogik (Einladungs-E-Mails) |
|
||
|
||
---
|
||
|
||
## 5. Multi-Tenancy Konzept
|
||
|
||
### Datenisolation via Row Level Security (RLS)
|
||
|
||
Jede Innung ist eine `organization`. Alle Tabellen haben eine `org_id` Spalte.
|
||
|
||
```sql
|
||
-- Beispiel RLS Policy für die members-Tabelle
|
||
CREATE POLICY "members_isolation" ON members
|
||
FOR ALL
|
||
USING (org_id = (
|
||
SELECT org_id FROM user_roles
|
||
WHERE user_id = auth.uid()
|
||
));
|
||
```
|
||
|
||
**Prinzip:**
|
||
- Kein Nutzer sieht Daten außerhalb seiner `org_id`
|
||
- Policy wird für jede Operation (SELECT, INSERT, UPDATE, DELETE) durchgesetzt
|
||
- Supabase prüft dies auf Datenbankebene — kein Bypass möglich
|
||
|
||
### Tenancy Identifikation
|
||
|
||
- **MVP:** `org_id` wird beim Login aus `user_roles` geladen und in allen Queries mitgegeben
|
||
- **Post-MVP:** Subdomain-Routing (`innung-elektro-stuttgart.innungsapp.de`) mit Middleware-Lookup
|
||
|
||
---
|
||
|
||
## 6. Authentifizierung
|
||
|
||
### Login Flow
|
||
|
||
```
|
||
Nutzer gibt E-Mail ein
|
||
│
|
||
▼
|
||
Supabase sendet Magic Link
|
||
│
|
||
▼
|
||
Nutzer klickt Link im E-Mail
|
||
│
|
||
▼
|
||
App/Browser öffnet sich, Token wird verarbeitet
|
||
│
|
||
▼
|
||
Supabase Auth gibt Session zurück (JWT)
|
||
│
|
||
▼
|
||
App lädt user_roles → bestimmt org_id und Rolle
|
||
│
|
||
▼
|
||
Redirect zu korrekter Startseite
|
||
```
|
||
|
||
### Rollen-System
|
||
|
||
```sql
|
||
CREATE TYPE user_role AS ENUM ('admin', 'member', 'public');
|
||
|
||
CREATE TABLE user_roles (
|
||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
user_id uuid REFERENCES auth.users NOT NULL,
|
||
org_id uuid REFERENCES organizations NOT NULL,
|
||
role user_role NOT NULL DEFAULT 'member',
|
||
created_at timestamptz DEFAULT now(),
|
||
UNIQUE(user_id, org_id)
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Push Notifications
|
||
|
||
### Architektur
|
||
|
||
```
|
||
Admin erstellt Beitrag
|
||
│
|
||
▼
|
||
Supabase Edge Function wird getriggert (via Database Webhook)
|
||
│
|
||
▼
|
||
Edge Function fetcht alle Push Tokens der org_id
|
||
│
|
||
▼
|
||
Expo Push Notification Service (EPNS)
|
||
│
|
||
┌──┴──┐
|
||
▼ ▼
|
||
APNs FCM
|
||
(iOS) (Android)
|
||
```
|
||
|
||
### Push Token Registrierung (Mobile)
|
||
|
||
```typescript
|
||
// hooks/usePushNotifications.ts
|
||
async function registerPushToken() {
|
||
const { status } = await Notifications.requestPermissionsAsync();
|
||
if (status !== 'granted') return;
|
||
|
||
const token = await Notifications.getExpoPushTokenAsync({
|
||
projectId: Constants.expoConfig.extra.eas.projectId,
|
||
});
|
||
|
||
await supabase
|
||
.from('push_tokens')
|
||
.upsert({ user_id: user.id, token: token.data });
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Infrastruktur & Kosten
|
||
|
||
### Monatliche Kosten (MVP, bis 100 Innungen)
|
||
|
||
| Service | Plan | Kosten/Monat |
|
||
|---|---|---|
|
||
| Supabase | Pro | 25 € |
|
||
| Vercel | Pro | 20 € |
|
||
| Resend (E-Mail) | Starter | 0 € (bis 3.000 Mails) |
|
||
| Expo EAS Build | Production | 29 € |
|
||
| PostHog | Cloud | 0 € (bis 1 Mio. Events) |
|
||
| Apple Developer | (jährlich) | 8 € |
|
||
| **Gesamt** | | **~82 €/Monat** |
|
||
|
||
### Skalierung (ab 500 Innungen)
|
||
|
||
| Service | Plan | Kosten/Monat |
|
||
|---|---|---|
|
||
| Supabase | Team | 599 € |
|
||
| Vercel | Enterprise | ~400 € |
|
||
| Resend | Business | 89 € |
|
||
| **Gesamt** | | **~1.100 €/Monat** |
|
||
|
||
Break-even bei 6 zahlenden Innungen à 200 €.
|
||
|
||
---
|
||
|
||
## 9. Deployment & CI/CD
|
||
|
||
### Pipeline
|
||
|
||
```
|
||
git push → GitHub/Gitea
|
||
│
|
||
├── Mobile: Expo EAS Build (iOS + Android)
|
||
│ └── App Store / Play Store (manual submit)
|
||
│
|
||
└── Web Admin: Vercel Deploy (automatisch)
|
||
└── Preview URL für jeden Branch
|
||
```
|
||
|
||
### Environments
|
||
|
||
| Environment | Supabase | Vercel | Verwendung |
|
||
|---|---|---|---|
|
||
| `development` | Lokal (Docker) | localhost:3000 | Entwicklung |
|
||
| `staging` | Staging-Projekt | staging.innungsapp.de | Pilot-Tests |
|
||
| `production` | Pro-Projekt | app.innungsapp.de | Live |
|