Files
HausApp/CLAUDE.md
René Schober 9ddc7c6d7a Production deployment setup + feature complete
- Dockerfile + deploy.sh for Hetzner server
- Email verification via Better Auth + Resend
- Invite code flow (6-digit OTP, generate/join)
- Settlement share percent fix (payer vs debtor)
- OCR scanner fixes (date display, retry, viewfinder)
- app.json icon/splash/adaptive-icon configured
- iOS deployment target 15.5 (ML Kit requirement)
- DB migration 0014: household_invitations table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:54:22 +01:00

182 lines
7.7 KiB
Markdown

# HaushaltsApp — Claude Instructions
## Projektübersicht
Mobile-SaaS App für Haushaltsverwaltung: Haushaltsbuch, Urlaubsbudget, Echtzeit-Einkaufsliste.
**Stack:**
- **Monorepo:** Bun Workspaces + Turborepo
- **Backend:** Hono (Bun) — `apps/server/`
- **Mobile:** React Native + Expo + expo-router — `apps/native/`
- **Web (Dashboard):** React + TanStack Router + Vite — `apps/web/`
- **Database:** PostgreSQL + Drizzle ORM — `packages/db/`
- **Auth:** Better Auth — `packages/auth/`
- **Shared Types/Schemas:** Zod — `packages/shared/`
- **UI (Web):** shadcn/ui (base-lyra) — `packages/ui/`
- **UI (Native):** HeroUI Native + Uniwind
## Architektur-Entscheidungen
- **Multi-Tenant via `householdId`:** Jeder Haushalt ist ein Tenant. Alle Daten (Transaktionen, Einkaufslisten etc.) sind an eine `householdId` gebunden. `householdId` wird via `x-household-id` HTTP-Header übergeben.
- **Subscription Plans:** `free`, `pro`, `family` — Feature-Gates in `plan.middleware.ts`, Definitionen in `packages/shared/src/constants/plans.ts`.
- **Better Auth** übernimmt User/Session-Management. Die `packages/auth/` enthält die Server-seitige Konfiguration.
- **Shared Package (`@haushaltsApp/shared`):** Zod-Schemas und TypeScript-Types werden im shared package definiert und sowohl vom Backend als auch vom Frontend verwendet.
- **Drizzle Schema:** Alle App-Tabellen in `packages/db/src/schema/app.ts`, Auth-Tabellen in `packages/db/src/schema/auth.ts`.
## Development starten
```bash
# Alle Services starten
bun run dev
# Einzelne Services
bun run dev:server # API auf http://localhost:3000
bun run dev:web # Web auf http://localhost:3001
bun run dev:native # Expo (Metro Bundler)
# Datenbank
bun run db:start # PostgreSQL via Docker starten
bun run db:generate # Drizzle Migrationen generieren
bun run db:migrate # Migrationen anwenden
bun run db:studio # Drizzle Studio öffnen
```
## Testing-Konventionen
### Backend (`apps/server/`)
- **Framework:** `bun:test` (built-in, kein extra Package)
- **Ort:** `apps/server/src/__tests__/routes/` und `apps/server/src/__tests__/services/`
- **Konvention:** Route-Tests testen den HTTP-Layer direkt via `app.request()`. Services werden unit-getestet.
- **Ausführen:** `bun run test:api` oder `bun test apps/server/src/__tests__`
```typescript
// Beispiel Route-Test
import { describe, expect, it } from "bun:test";
import app from "../../index";
describe("GET /health", () => {
it("returns 200 with status ok", async () => {
const res = await app.request("/health");
expect(res.status).toBe(200);
});
});
```
### Mobile (`apps/native/`)
- **Framework:** `bun:test` — kein Jest/Babel/jest-expo (Jest + Bun's .bun/ Store sind inkompatibel mit RN's ESM setup files)
- **Was wird getestet:** Stores, Hooks, Utils, API-Client — reine Business Logic, kein React Native Rendering
- **Was NICHT getestet wird:** UI-Komponenten, Screens — Rendering wird manuell via Expo Go verifiziert
- **Ort:** `apps/native/src/__tests__/` spiegelt `apps/native/src/` Struktur
- **Ausführen:** `bun run test:mobile` oder `bun test apps/native/src/__tests__`
```typescript
// Beispiel Store-Test
import { describe, expect, it, beforeEach } from "bun:test";
import { useAuthStore } from "../../stores/auth.store";
describe("authStore", () => {
beforeEach(() => {
useAuthStore.setState({ user: null, token: null, isAuthenticated: false });
});
it("setUser authenticates the user", () => {
useAuthStore.getState().setUser({ id: "1", name: "Test", email: "t@t.com" });
expect(useAuthStore.getState().isAuthenticated).toBe(true);
});
});
```
## Coding-Konventionen
### Allgemein
- **TypeScript strict mode** überall — kein `any`, kein type-casting ohne Kommentar
- **Zod-first:** Alle API-Inputs werden mit Zod-Schemas aus `@haushaltsApp/shared` validiert
- **Named exports** bevorzugen (default exports nur bei Expo/React Router Screens)
### Naming
- **Files:** `kebab-case.ts` / `PascalCase.tsx` für React-Komponenten
- **Variables/Functions:** `camelCase`
- **Types/Interfaces:** `PascalCase`
- **Database tables:** `snake_case` (Drizzle convention)
- **Zod schemas:** `camelCaseSchema` (z.B. `createTransactionSchema`)
- **Route files:** `feature.routes.ts`
- **Middleware files:** `feature.middleware.ts`
- **Service files:** `feature.service.ts`
### API-Design
- Alle Endpoints unter `/api/` prefix
- Auth-Check via `authMiddleware` + `requireAuth`
- Tenant-Check via `tenantMiddleware` + `requireHousehold`
- Plan-Gates via `requireFeature('featureName')`
- HTTP-Status-Codes: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
### Ordnerstruktur (Backend)
```
apps/server/src/
├── routes/ # HTTP-Layer, minimale Logik
├── services/ # Business Logic, kein direkter DB-Code in routes
├── middleware/ # Auth, Tenant, Plan Feature Gates
├── websocket/ # WebSocket Handler
├── lib/ # DB-Instance, Utilities
└── __tests__/ # Tests spiegeln src/-Struktur
```
### State Management (Mobile)
- **Zustand** für globalen Client-State (User, aktueller Haushalt)
- **TanStack Query** für Server-State (Daten aus der API)
- Kein direktes Fetch in Komponenten — immer Custom Hooks oder TanStack Query
## Known Constraints
### React Native Testing
Jest + Bun's Content-Addressable Store (`.bun/`) sind strukturell inkompatibel für RN Rendering-Tests. `jest-expo` setzt `setupFiles` mit absoluten `.bun/` Pfaden — kein Resolver oder `transformIgnorePatterns` kann das abfangen.
**Entscheidung:** `bun:test` für Business Logic (Stores, Hooks, Utils, Services). Rendering wird via Expo Go verifiziert. Rendering-Tests werden nachgereicht wenn Bun/jest-expo das nativ lösen.
### Web App (`apps/web`)
TanStack Router generiert `routeTree.gen.ts` erst bei `vite dev`. `check-types` für web daher nur nach einmaligem dev-Run aussagekräftig.
### Better Auth Mobile Setup
- `bearer` Plugin ist Pflicht in `packages/auth/src/index.ts`
- Mobile Clients nutzen `Authorization: Bearer <token>` Header
- Ohne `bearer` Plugin: `getSession()` gibt `null` zurück für alle Mobile Requests — stiller Auth-Fehler, alle Requests landen als 401
### Expo Router — Redirect Pattern (FINAL)
**FALSCH:** Session-Guards in mehreren Layouts gleichzeitig → Ping-Pong Loop zwischen `(auth)` und `(app)` Layout
**RICHTIG:**
- `index.tsx`: statischer `<Redirect href="/(auth)/login" />` — kein useSession, kein useEffect
- `(auth)/_layout.tsx`: **KEIN Guard**, nur Stack-Definition
- `(app)/_layout.tsx`: **EINZIGER Guard**
- kein session → `/(auth)/login`
- kein householdId → `/(auth)/onboarding`
- sonst: render children
Ein Guard, eine Quelle der Wahrheit.
### Household / Organization Bridge
- `households.id === Better Auth organization.id` (gleiche UUID)
- Reihenfolge beim Onboarding:
1. `organization.create()``organizationId`
2. `INSERT INTO households { id: organizationId, ... }` (via `/api/households/setup`)
3. `seedDefaultCategories(organizationId)`
- Kein harter DB-FK von `households` zu `organization` — application-level check reicht, Better Auth Schema ist extern
## Projektstand
| Phase | Status | Details |
|-------|--------|---------|
| Phase 1 — Foundation | ✅ | Monorepo, DB Schema, Shared Types, Stubs |
| Phase 2 — Auth Flow | ✅ | Better Auth, Organization Plugin, Apple, Auth Screens |
| Phase 3 — Transactions Full Stack | ✅ | 9 API Tests, 7 Mobile Tests, Tenant Isolation bestätigt |
| Phase 4 — Dashboard | ⬜ | |
| Phase 5 — Urlaubsbudget | ⬜ | |
| Phase 6 — OCR Scanner | ⬜ | |
| Phase 7 — Einkaufsliste (WebSockets) | ⬜ | |
## Skills
Projekt-spezifische Skills unter `apps/api/.agents/skills/` (noch leer, wird befüllt).
@apps/api/.agents/skills/