d0a40bde88
* ci: prevent duplicate test runs - tests only on PRs, inline tests for builds * docs: add testing and CI/CD documentation * security: fix CodeQL vulnerabilities (SSRF, XSS, rate limiting) - Add URL validation to prevent SSRF attacks on notification endpoints - Block private IPs (10.x, 172.16-31.x, 192.168.x, 169.254.x) - Block localhost and internal hostnames - Only allow HTTP/HTTPS protocols - Add HTML escaping for medication names in email templates (XSS) - Add stricter rate limiting for auth routes (5 req/15min for login/register) - Add SSRF protection tests (405 tests total) * security: add rate limiting to remaining auth routes * chore: add CodeQL config to suppress rate-limit false positives Rate limiting IS implemented via @fastify/rate-limit plugin: - Global: 100 req/min (index.ts) - Auth routes: 5-10 req/min via config.rateLimit option CodeQL doesn't recognize Fastify's plugin-based rate limiting pattern. * ci: switch to CodeQL Advanced Setup - Add custom codeql.yml workflow - Configure to use codeql-config.yml - Exclude js/missing-rate-limiting rule (false positive) Rate limiting is implemented via @fastify/rate-limit plugin * ci: add explicit permissions to workflows Fixes CodeQL 'Workflow does not contain permissions' warnings. Sets minimal 'contents: read' at top level. * ci: add manual trigger to CodeQL workflow * ci: add explicit permissions to all workflow jobs * build(deps): bump esbuild, @vitest/coverage-v8 and vitest in /backend Bumps [esbuild](https://github.com/evanw/esbuild) to 0.27.2 and updates ancestor dependencies [esbuild](https://github.com/evanw/esbuild), [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). These dependencies need to be updated together. Updates `esbuild` from 0.21.5 to 0.27.2 - [Release notes](https://github.com/evanw/esbuild/releases) - [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md) - [Commits](https://github.com/evanw/esbuild/compare/v0.21.5...v0.27.2) Updates `@vitest/coverage-v8` from 2.1.9 to 4.0.16 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/coverage-v8) Updates `vitest` from 2.1.9 to 4.0.16 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/vitest) --- updated-dependencies: - dependency-name: esbuild dependency-version: 0.27.2 dependency-type: indirect - dependency-name: "@vitest/coverage-v8" dependency-version: 4.0.16 dependency-type: direct:development - dependency-name: vitest dependency-version: 4.0.16 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> * docs: add GitHub issue templates - Bug report template with deployment type, browser info, logs - Feature request template with affected area, priority - Config with link to discussions and README - Optimize test.yml to skip tests for non-code changes * Initial plan * Remove database schema duplication by creating shared schema-sql.ts module Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Refactor frontend date formatting to eliminate duplication Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * docs: Add branch protection warning and PR workflow to instructions * ci: remove paths filter from test workflow to fix branch protection * fix: add .js extension to schema-sql imports for ESM compatibility (#15) * feat: add setting to skip reminders for taken doses - Add skipRemindersForTakenDoses setting to database schema - Extend settings API to save and load new setting - Update intake reminder scheduler to filter taken doses - Add frontend toggle in settings with i18n (EN/DE) - Only check doses from today (timezone-aware) - Update all test schemas with new field - All 405 tests passing * feat: add repeat reminders for missed doses - Add repeatRemindersEnabled and reminderRepeatIntervalMinutes settings - Refactor intake reminder state from array to object with sendCount tracking - Update scheduler to send repeated reminders at configurable intervals - Only remind for today's doses (timezone-aware filtering) - Add frontend toggle and interval input (5-480 minutes) in settings - Maintain backward compatibility for old state file format - Update all test schemas and assertions - All 406 tests passing * feat: add nagging reminders with max limit and ENV defaults - Add maxNaggingReminders setting to limit repeat reminders (1-20) - Add ENV defaults for all user settings (DEFAULT_*) - Add ALTER TABLE migrations for backward compatibility - Add smtpConfigured/shoutrrrConfigured to health endpoint - Fix Push toggle to allow enabling without existing URL - Disable skip/repeat toggles when no notifications enabled - Add Pocket ID button to registration page - Add getTodaysIntakes() for repeat reminder logic - Update translations (en/de) for new settings - Add comprehensive tests for new features --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com>
439 lines
18 KiB
Markdown
439 lines
18 KiB
Markdown
# MedAssist-ng - AI Coding Instructions
|
|
|
|
## General Rules
|
|
|
|
- **No temporary files**: Delete temporary scripts/files immediately after use. Do not commit temporary debug scripts, test files, or one-off utilities to the repository.
|
|
- **Clean workspace**: Always clean up after yourself. If you create a file for a specific task, delete it once done.
|
|
|
|
## Architecture Overview
|
|
|
|
MedAssist-ng is a **medication tracking and planning app** with a monorepo structure:
|
|
|
|
- **Backend**: Fastify 5 + TypeScript + SQLite (Drizzle ORM) at `backend/`
|
|
- **Frontend**: React 18 + Vite + TypeScript at `frontend/`
|
|
- **Database**: SQLite with migrations in `backend/src/db/migrations/`
|
|
- **Deployment**: Docker Compose with separate dev containers
|
|
- **i18n**: English (en) and German (de) via react-i18next
|
|
|
|
### Data Flow
|
|
```
|
|
Frontend (React) → /api/* proxy → Backend (Fastify) → SQLite
|
|
↓ (Vite rewrites /api to /)
|
|
```
|
|
|
|
The Vite proxy at `frontend/vite.config.ts` rewrites `/api/*` to `/` - so frontend calls `/api/medications` but backend route is just `/medications`.
|
|
|
|
## Development Commands
|
|
|
|
```bash
|
|
# Start dev environment (preferred)
|
|
docker compose -f docker-compose.dev.yml up
|
|
|
|
# Or run services separately:
|
|
cd backend && npm run dev # tsx watch on port 3000
|
|
cd frontend && npm run dev # Vite on port 5173
|
|
|
|
# Production
|
|
docker compose up -d
|
|
|
|
# Database migrations
|
|
cd backend && npm run migrate
|
|
|
|
# Run tests
|
|
cd backend && npm test # Run all tests
|
|
cd backend && npm run test:coverage # Run with coverage report
|
|
```
|
|
|
|
## Testing (MANDATORY)
|
|
|
|
> ⚠️ **WICHTIG**: Jede neue Funktionalität MUSS mit Tests abgedeckt werden!
|
|
> Pull Requests ohne Tests für neue Features werden nicht akzeptiert.
|
|
|
|
### Test-Framework
|
|
- **Vitest 2.1** mit v8 Coverage
|
|
- Tests in `backend/src/test/*.test.ts`
|
|
- Coverage-Ziel: Mindestens gleiche oder bessere Coverage nach Änderungen
|
|
|
|
### Test-Struktur
|
|
| Datei | Testet |
|
|
|-------|--------|
|
|
| `routes.test.ts` | API-Endpunkte (Auth, Medications, Doses, Settings, Share, Planner) |
|
|
| `services.test.ts` | Scheduler-Utilities (Timezone, Blisters, Usage-Berechnung) |
|
|
| `db.test.ts` | Datenbank-Schema und Operationen |
|
|
|
|
### Tests schreiben
|
|
|
|
```typescript
|
|
// Backend Test Beispiel (backend/src/test/example.test.ts)
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import { createTestApp, createTestUser } from './routes.test'; // Test-Utilities
|
|
|
|
describe('Feature Name', () => {
|
|
let app: FastifyInstance;
|
|
let authToken: string;
|
|
|
|
beforeAll(async () => {
|
|
app = await createTestApp();
|
|
const user = await createTestUser(app);
|
|
authToken = user.token;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
it('should do something specific', async () => {
|
|
const response = await app.inject({
|
|
method: 'GET',
|
|
url: '/endpoint',
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
expect(response.json()).toHaveProperty('expectedField');
|
|
});
|
|
});
|
|
```
|
|
|
|
### Test-Commands
|
|
```bash
|
|
cd backend
|
|
CI=true npm test # Tests einmal ausführen (IMMER so ausführen!)
|
|
CI=true npm run test:coverage # Mit Coverage-Report
|
|
npm test -- --watch # Watch-Mode für manuelle Entwicklung
|
|
npm test -- -t "test name" # Einzelnen Test ausführen
|
|
```
|
|
|
|
> ⚠️ **WICHTIG für AI-Agenten**: Tests IMMER mit `CI=true` ausführen!
|
|
> Ohne `CI=true` läuft Vitest im Watch-Mode und wartet auf Eingaben.
|
|
|
|
## CI/CD Pipeline (GitHub Actions)
|
|
|
|
### Workflow-Übersicht
|
|
|
|
```
|
|
Pull Request erstellt
|
|
↓
|
|
┌─────────────────────────────────────┐
|
|
│ test.yml │
|
|
│ ├─ backend-test (parallel) │
|
|
│ │ ├─ npm ci │
|
|
│ │ ├─ tsc --noEmit (Type-Check) │
|
|
│ │ └─ npm run test:coverage │
|
|
│ └─ frontend-build (parallel) │
|
|
│ ├─ npm ci │
|
|
│ └─ npm run build │
|
|
└─────────────────────────────────────┘
|
|
↓ Tests müssen bestehen
|
|
PR kann gemerged werden
|
|
↓
|
|
Push to main / Tag erstellt
|
|
↓
|
|
┌─────────────────────────────────────┐
|
|
│ docker-build.yml │
|
|
│ ├─ backend-test (parallel) │
|
|
│ ├─ frontend-build (parallel) │
|
|
│ └─ build-and-push (nach Tests) │
|
|
│ ├─ Docker Images bauen │
|
|
│ └─ Push zu GHCR │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
### Branch Protection
|
|
|
|
> ⚠️ **WICHTIG**: Der `main` Branch ist geschützt!
|
|
> Direktes Pushen nach `main` ist **nicht möglich** - GitHub lehnt den Push ab.
|
|
> Alle Änderungen müssen über Pull Requests erfolgen.
|
|
|
|
- **main** Branch ist geschützt (Repository Rules)
|
|
- Direktes Pushen wird von GitHub abgelehnt mit: `GH013: Repository rule violations`
|
|
- PRs benötigen:
|
|
- ✅ `backend-test` Status Check bestanden
|
|
- ✅ `frontend-build` Status Check bestanden
|
|
- Nach erfolgreichem Merge wird der Feature-Branch automatisch gelöscht
|
|
|
|
**Workflow für Änderungen:**
|
|
```bash
|
|
# 1. Feature Branch erstellen
|
|
git checkout -b feat/mein-feature
|
|
|
|
# 2. Änderungen committen und pushen
|
|
git add . && git commit -m "feat: Beschreibung"
|
|
git push -u origin feat/mein-feature
|
|
|
|
# 3. PR erstellen (via GitHub CLI oder Web)
|
|
gh pr create --title "Mein Feature" --body "Beschreibung"
|
|
|
|
# 4. Warten bis CI grün ist, dann mergen
|
|
gh pr merge --squash --delete-branch
|
|
```
|
|
|
|
### Workflow-Dateien
|
|
| Datei | Trigger | Zweck |
|
|
|-------|---------|-------|
|
|
| `.github/workflows/test.yml` | Pull Requests | Tests ausführen, PR blockieren bei Fehlern |
|
|
| `.github/workflows/docker-build.yml` | Push to main, Tags | Tests + Docker Images bauen und pushen |
|
|
|
|
### Neuen Code hinzufügen - Checkliste
|
|
1. ✅ Feature implementieren
|
|
2. ✅ Tests für das Feature schreiben
|
|
3. ✅ Lokal `npm run test:coverage` ausführen
|
|
4. ✅ Coverage darf nicht sinken
|
|
5. ✅ Feature Branch erstellen und pushen
|
|
6. ✅ Pull Request erstellen
|
|
7. ✅ Warten bis CI grün ist
|
|
8. ✅ PR mergen (Branch wird automatisch gelöscht)
|
|
|
|
## Key Patterns
|
|
|
|
### Backend Routes (`backend/src/routes/`)
|
|
| Route File | Endpoints |
|
|
|------------|-----------|
|
|
| `auth.ts` | `/auth/login`, `/auth/register`, `/auth/logout`, `/auth/refresh`, `/auth/me` |
|
|
| `medications.ts` | CRUD `/medications`, `/medications/:id/image` |
|
|
| `doses.ts` | `/doses/taken` - track dose intake |
|
|
| `planner.ts` | `/medications/usage` - calculate usage for date range |
|
|
| `settings.ts` | `/settings` - user settings CRUD |
|
|
| `share.ts` | `/share` - create share tokens, `/share/:token` - public access |
|
|
| `health.ts` | `/health` - health check endpoint |
|
|
|
|
### Backend Services (`backend/src/services/`)
|
|
| Service | Description |
|
|
|---------|-------------|
|
|
| `reminder-scheduler.ts` | Stock reminder emails/push notifications |
|
|
| `intake-reminder-scheduler.ts` | Intake reminder notifications |
|
|
|
|
### Frontend (`frontend/src/App.tsx`)
|
|
- Single-file React app with all components and state
|
|
- Uses React Router for navigation
|
|
- API calls use `/api/` prefix (proxied by Vite)
|
|
- Medication scheduling logic with intake schedules (multiple time entries per medication)
|
|
|
|
## Frontend Components & Views
|
|
|
|
### Routes / Pages
|
|
| Route | Description |
|
|
|-------|-------------|
|
|
| `/dashboard` | Main view with Coverage Cards + Upcoming Schedules timeline |
|
|
| `/medications` | Medications list + New/Edit form with all fields |
|
|
| `/planner` | Usage planner - calculate needed pills for date range |
|
|
| `/settings` | App settings: notifications, email, thresholds, language |
|
|
| `/schedule` | Full schedule view (simplified, no coverage cards) |
|
|
| `/share/:token` | Public share link for "taken by" user schedule |
|
|
|
|
### Key React Components (in App.tsx)
|
|
| Component | Description |
|
|
|-----------|-------------|
|
|
| `App` | Root component with BrowserRouter |
|
|
| `AppRouter` | Handles auth check, renders AppContent or Auth |
|
|
| `AppContent` | Main app shell with navigation, header, all routes |
|
|
| `SharedSchedule` | Public share page for medication schedules by person |
|
|
| `MedicationAvatar` | Round avatar with medication image or colored initial |
|
|
|
|
### Dashboard Sections
|
|
| Section | Description |
|
|
|---------|-------------|
|
|
| **Coverage Cards** | Stock status cards per medication: days left, blisters, status (Normal/Warning/Critical) |
|
|
| **Upcoming Schedules** | Timeline grouped by day, collapsible days, dose tracking |
|
|
|
|
### Schedule/Timeline Elements
|
|
| Element | CSS Class | Description |
|
|
|---------|-----------|-------------|
|
|
| Past days toggle | `.past-days-toggle` | Click to show/hide past days |
|
|
| Day container | `.day-block` | Container for one day, collapsible |
|
|
| Today highlight | `.day-block.today` | Blue border/background for current day |
|
|
| Past day | `.day-block.past` | Dashed border, reduced opacity |
|
|
| All taken | `.day-block.all-taken` | Green styling when all doses taken |
|
|
| Day header | `.day-divider` | Date header with collapse toggle arrow |
|
|
| Collapse icon | `.day-collapse-icon` | ▶/▼ arrow for expand/collapse |
|
|
| Day summary | `.day-summary` | Shows "X/Y" doses taken or "✓ All taken" |
|
|
| Medication row | `.time-row` | One medication's doses for that day |
|
|
| Dose item | `.dose-item` | Individual dose with time, amount, take/undo button |
|
|
| Dose taken | `.dose-item.taken` | Green background when dose is marked taken |
|
|
| Dose overdue | `.dose-item.overdue` | Styling for past untaken doses |
|
|
| Dose future | `.dose-item.future` | Disabled button for future days |
|
|
|
|
### Medication Form (New/Edit)
|
|
| Field | Description |
|
|
|-------|-------------|
|
|
| Commercial Name | Main medication name (required) |
|
|
| Generic Name | Scientific/generic name (optional) |
|
|
| Taken By | Person taking the medication (optional, enables filtering/sharing) |
|
|
| Packs | Number of full packs |
|
|
| Blisters per Pack | Strips/blisters in each pack |
|
|
| Pills per Blister | Tablets per strip |
|
|
| Loose Pills | Extra pills not in blisters |
|
|
| Pill Weight (mg) | Weight per pill for dose calculation display |
|
|
| Expiry Date | Medication expiration |
|
|
| Notes | Free text notes |
|
|
| Image Upload | Medication photo (preview for new, direct upload for edit) |
|
|
| **Intake Schedule** | One or more intake entries defining usage pattern |
|
|
|
|
### Intake Schedule
|
|
Each blister defines a recurring intake:
|
|
- **Usage (Pills)**: How many pills per dose
|
|
- **Every (Days)**: Interval (1 = daily, 7 = weekly)
|
|
- **Start (Date/Time)**: When the schedule starts (determines past/future doses)
|
|
- **Remind checkbox**: Enable intake reminders (🔔)
|
|
|
|
### Modals
|
|
| Modal | Trigger | Content |
|
|
|-------|---------|---------|
|
|
| Medication Detail | Click on coverage card or medication row | Full medication info, stock, schedule preview, edit/delete/ICS buttons |
|
|
| Image Lightbox | Click medication image | Full-size medication image |
|
|
| Share Dialog | "Share" button on schedules | Generate share link for specific "taken by" person |
|
|
| User Schedule Filter | Click on "taken by" badge | Filter schedule by person |
|
|
|
|
### Settings Sections
|
|
| Section | Settings |
|
|
|---------|----------|
|
|
| General | Language toggle (EN/DE) |
|
|
| Stock Thresholds | Warning days, critical days, expiry warning days |
|
|
| Email Notifications | Enable, email address, stock/intake toggles |
|
|
| Push Notifications (Shoutrrr) | Enable, URL (ntfy/gotify/etc), stock/intake toggles |
|
|
| Reminder Settings | Days before, repeat daily, skip for taken, repeat/nagging |
|
|
| SMTP | Email config (read-only from .env) |
|
|
|
|
### Settings ENV Defaults
|
|
All user settings can be pre-configured via ENV variables (see `.env.example`).
|
|
These are only used as **defaults when a new user is created**.
|
|
Once a user saves settings in the app, their saved values take precedence over ENV.
|
|
|
|
| ENV Variable | Setting | Default |
|
|
|--------------|---------|---------|
|
|
| `DEFAULT_EMAIL_ENABLED` | Email notifications | false |
|
|
| `DEFAULT_SHOUTRRR_ENABLED` | Push notifications | false |
|
|
| `DEFAULT_SHOUTRRR_URL` | ntfy/gotify URL | (empty) |
|
|
| `DEFAULT_REPEAT_REMINDERS_ENABLED` | Nagging reminders | false |
|
|
| `DEFAULT_REMINDER_REPEAT_INTERVAL_MINUTES` | Nag interval | 30 |
|
|
| `DEFAULT_MAX_NAGGING_REMINDERS` | Max nags | 5 |
|
|
| `DEFAULT_LOW_STOCK_DAYS` | Low stock threshold | 30 |
|
|
| `DEFAULT_LANGUAGE` | UI language | en |
|
|
|
|
## Database Schema (`backend/src/db/schema.ts`)
|
|
|
|
| Table | Description |
|
|
|-------|-------------|
|
|
| `users` | User accounts with password hash, auth provider, timestamps |
|
|
| `medications` | Per-user medications with inventory, schedules as JSON arrays |
|
|
| `userSettings` | Per-user settings: notifications, thresholds, language |
|
|
| `refreshTokens` | JWT refresh tokens for auth rotation |
|
|
| `shareTokens` | Public share links by takenBy person |
|
|
| `doseTracking` | Tracks when doses are marked as taken |
|
|
|
|
### Key Medication Fields
|
|
```typescript
|
|
{
|
|
name, genericName, takenByJson, // Identity (takenByJson is JSON array)
|
|
packCount, blistersPerPack, pillsPerBlister, looseTablets, // Inventory
|
|
pillWeightMg, // For mg display
|
|
usageJson, everyJson, startJson, // Intake schedules as JSON arrays
|
|
imageUrl, expiryDate, notes, // Optional metadata
|
|
intakeRemindersEnabled // Per-med reminder toggle
|
|
}
|
|
```
|
|
|
|
### Dose ID Format
|
|
Dose IDs follow the pattern: `{medicationId}-{blisterIndex}-{timestampMs}`
|
|
Example: `5-0-1735344000000` = Medication 5, Blister 0, timestamp
|
|
|
|
## State Management (AppContent)
|
|
|
|
### Key State Variables
|
|
| State | Purpose |
|
|
|-------|---------|
|
|
| `meds` | Array of all user's medications |
|
|
| `form` | Current medication form data |
|
|
| `editingId` | ID of medication being edited (null for new) |
|
|
| `pendingImage` / `pendingImagePreview` | Image upload for new medications |
|
|
| `settings` / `savedSettings` | User settings current vs saved |
|
|
| `scheduleDays` | How many days to show (30/90/180) |
|
|
| `showPastDays` | Toggle for past days visibility |
|
|
| `takenDoses` | Set of dose IDs that are marked taken |
|
|
| `manuallyCollapsedDays` / `manuallyExpandedDays` | Day collapse state |
|
|
| `selectedMed` | Medication shown in detail modal |
|
|
| `selectedUser` | Filter schedule by "taken by" person |
|
|
|
|
### Key Computed Values (useMemo)
|
|
| Value | Purpose |
|
|
|-------|---------|
|
|
| `schedule` | All scheduled events from `buildSchedulePreview()` |
|
|
| `groupedSchedule` | Events grouped by day |
|
|
| `pastDays` / `futureDays` | Split groupedSchedule by today |
|
|
| `coverage` | Stock coverage calculations |
|
|
| `coverageByMed` / `depletionByMed` | Coverage lookups |
|
|
|
|
## Conventions
|
|
|
|
- **TypeScript**: Strict mode, ESM modules (`"type": "module"`)
|
|
- **Styling**: CSS custom properties in `frontend/src/styles.css`, dark/light theme via `data-theme`
|
|
- **API responses**: Return objects directly, Fastify serializes to JSON
|
|
- **Environment**: Copy `.env.example` → `.env`, secrets must be 10+ chars
|
|
- **i18n**: All UI text via `t('key')` function, translations in `frontend/src/i18n/*.json`
|
|
|
|
## Database Schema Changes (WICHTIG: Abwärtskompatibilität!)
|
|
|
|
> ⚠️ **KRITISCH**: Die App MUSS abwärtskompatibel mit älteren Datenbanken bleiben!
|
|
> Nutzer upgraden ihre Docker-Container, aber behalten ihre bestehende DB.
|
|
> Die App darf NICHT abstürzen wenn alte Spalten fehlen.
|
|
|
|
### Regeln für neue Spalten
|
|
|
|
1. **IMMER mit DEFAULT-Wert**: Neue Spalten müssen `NOT NULL DEFAULT <wert>` haben
|
|
2. **NULL-sicher im Code**: Alle Abfragen müssen `?? defaultValue` oder `?? false` verwenden
|
|
3. **Schema-SQL aktualisieren**: In diesen Dateien hinzufügen:
|
|
- `backend/src/db/schema.ts` - Drizzle Schema
|
|
- `backend/src/db/schema-sql.ts` - `getTableCreationSQL()` für neue DBs
|
|
- `backend/src/db/client.ts` - `ALTER TABLE ADD COLUMN IF NOT EXISTS` Migration
|
|
4. **Test-Schemas updaten**: Alle Test-Dateien mit eigenem Schema:
|
|
- `backend/src/test/e2e-routes.test.ts`
|
|
- `backend/src/test/integration.test.ts`
|
|
- `backend/src/test/planner.test.ts`
|
|
|
|
### Beispiel: Neue Spalte hinzufügen
|
|
|
|
```typescript
|
|
// 1. schema.ts - Drizzle Definition
|
|
maxNaggingReminders: integer("max_nagging_reminders").notNull().default(5),
|
|
|
|
// 2. schema-sql.ts - Für neue Datenbanken
|
|
"max_nagging_reminders integer NOT NULL DEFAULT 5,"
|
|
|
|
// 3. client.ts - Migration für bestehende DBs (IN ensureTablesExist())
|
|
await client.execute(`ALTER TABLE user_settings ADD COLUMN max_nagging_reminders integer NOT NULL DEFAULT 5`).catch(() => {});
|
|
|
|
// 4. Routes - NULL-sicher lesen
|
|
maxNaggingReminders: settings.maxNaggingReminders ?? 5,
|
|
```
|
|
|
|
### Was NICHT erlaubt ist
|
|
|
|
- ❌ Spalten löschen oder umbenennen (bricht alte DBs)
|
|
- ❌ `NOT NULL` ohne `DEFAULT` (INSERT schlägt fehl)
|
|
- ❌ Spalten ohne Fallback im Code lesen
|
|
- ❌ DB löschen als "Lösung" dokumentieren
|
|
|
|
### Wann Abwärtskompatibilität NICHT möglich ist
|
|
|
|
Wenn eine Breaking Change unvermeidbar ist:
|
|
1. **Explizit kommunizieren**: In Release Notes dokumentieren
|
|
2. **Migration-Script**: Automatisches Upgrade-Script bereitstellen
|
|
3. **Versionsprüfung**: App sollte DB-Version prüfen und warnen
|
|
|
|
## File Locations
|
|
|
|
| Purpose | Location |
|
|
|---------|----------|
|
|
| Backend entry | `backend/src/index.ts` |
|
|
| Database schema | `backend/src/db/schema.ts` |
|
|
| Backend routes | `backend/src/routes/*.ts` |
|
|
| Backend services | `backend/src/services/*.ts` |
|
|
| Frontend app | `frontend/src/App.tsx` |
|
|
| Frontend auth | `frontend/src/components/Auth.tsx` |
|
|
| Styles | `frontend/src/styles.css` |
|
|
| i18n English | `frontend/src/i18n/en.json` |
|
|
| i18n German | `frontend/src/i18n/de.json` |
|
|
| Docker prod | `docker-compose.yml` |
|
|
| Docker dev | `docker-compose.dev.yml` |
|
|
| Env template | `.env.example` |
|