mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 16:19:39 +02:00
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
568 lines
14 KiB
Markdown
568 lines
14 KiB
Markdown
# App-Stabilitäts- und Testing-Strategie für ulo.ad
|
|
|
|
## Executive Summary
|
|
|
|
Dieser Bericht analysiert die aktuellen Stabilitätsprobleme der ulo.ad-Anwendung und bietet konkrete Lösungsansätze für eine robuste, fehlerfreie Produktionsumgebung. Die identifizierten Probleme (fehlerhafte PocketBase-Rules, inkonsistente Feldnamen) hätten durch systematisches Testing verhindert werden können.
|
|
|
|
## Identifizierte Probleme
|
|
|
|
### 1. Datenbankebene
|
|
|
|
- **Problem**: Inkonsistente Feldnamen zwischen Code und Datenbank (`user` vs `user_id`)
|
|
- **Auswirkung**: Funktionen schlagen in Produktion fehl, obwohl sie lokal funktionieren
|
|
- **Root Cause**: Fehlende Schema-Validierung und Integrationstests
|
|
|
|
### 2. API Rules
|
|
|
|
- **Problem**: Falsche PocketBase Collection Rules (z.B. `user_id = @request.auth.id` in createRule)
|
|
- **Auswirkung**: Benutzer können keine Tags/Ordner erstellen
|
|
- **Root Cause**: Keine automatisierten Tests für API-Berechtigungen
|
|
|
|
## Empfohlene Testing-Strategie
|
|
|
|
### 1. Unit Tests (Vitest)
|
|
|
|
```typescript
|
|
// tests/unit/pocketbase.spec.ts
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { pb, generateTagSlug, generateShortCode } from '$lib/pocketbase';
|
|
|
|
describe('PocketBase Utilities', () => {
|
|
describe('generateTagSlug', () => {
|
|
it('should generate valid slugs', () => {
|
|
expect(generateTagSlug('My Tag')).toBe('my-tag');
|
|
expect(generateTagSlug('Special!@#$Tag')).toBe('special-tag');
|
|
expect(generateTagSlug(' Trimmed ')).toBe('trimmed');
|
|
});
|
|
});
|
|
|
|
describe('generateShortCode', () => {
|
|
it('should generate codes of correct length', () => {
|
|
const code = generateShortCode(8);
|
|
expect(code).toHaveLength(8);
|
|
expect(code).toMatch(/^[a-zA-Z0-9]+$/);
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
### 2. Integration Tests
|
|
|
|
```typescript
|
|
// tests/integration/tags.spec.ts
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { pb } from '$lib/pocketbase';
|
|
|
|
describe('Tags Integration', () => {
|
|
let testUser;
|
|
|
|
beforeEach(async () => {
|
|
// Setup test user
|
|
testUser = await pb.collection('users').create({
|
|
email: `test-${Date.now()}@example.com`,
|
|
password: 'testpassword123',
|
|
passwordConfirm: 'testpassword123',
|
|
});
|
|
|
|
await pb.collection('users').authWithPassword(testUser.email, 'testpassword123');
|
|
});
|
|
|
|
it('should create a tag successfully', async () => {
|
|
const tag = await pb.collection('tags').create({
|
|
name: 'Test Tag',
|
|
slug: 'test-tag',
|
|
user_id: testUser.id,
|
|
color: '#3B82F6',
|
|
is_public: false,
|
|
});
|
|
|
|
expect(tag.name).toBe('Test Tag');
|
|
expect(tag.user_id).toBe(testUser.id);
|
|
});
|
|
|
|
it('should enforce user ownership on update', async () => {
|
|
const tag = await pb.collection('tags').create({
|
|
name: 'My Tag',
|
|
slug: 'my-tag',
|
|
user_id: testUser.id,
|
|
});
|
|
|
|
// Try to update with different user
|
|
const otherUser = await pb.collection('users').create({
|
|
email: `other-${Date.now()}@example.com`,
|
|
password: 'testpassword123',
|
|
passwordConfirm: 'testpassword123',
|
|
});
|
|
|
|
await pb.collection('users').authWithPassword(otherUser.email, 'testpassword123');
|
|
|
|
await expect(pb.collection('tags').update(tag.id, { name: 'Hacked' })).rejects.toThrow();
|
|
});
|
|
});
|
|
```
|
|
|
|
### 3. E2E Tests (Playwright)
|
|
|
|
```typescript
|
|
// e2e/auth.test.ts
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Authentication Flow', () => {
|
|
test('should register a new user', async ({ page }) => {
|
|
await page.goto('/register');
|
|
|
|
const email = `test-${Date.now()}@example.com`;
|
|
await page.fill('input[name="email"]', email);
|
|
await page.fill('input[name="password"]', 'SecurePass123!');
|
|
await page.fill('input[name="passwordConfirm"]', 'SecurePass123!');
|
|
await page.fill('input[name="username"]', `user${Date.now()}`);
|
|
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Should redirect to dashboard
|
|
await expect(page).toHaveURL('/dashboard');
|
|
await expect(page.locator('text=Welcome')).toBeVisible();
|
|
});
|
|
|
|
test('should login existing user', async ({ page }) => {
|
|
await page.goto('/login');
|
|
|
|
await page.fill('input[name="email"]', 'existing@example.com');
|
|
await page.fill('input[name="password"]', 'password123');
|
|
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page).toHaveURL('/dashboard');
|
|
});
|
|
|
|
test('should handle login errors', async ({ page }) => {
|
|
await page.goto('/login');
|
|
|
|
await page.fill('input[name="email"]', 'wrong@example.com');
|
|
await page.fill('input[name="password"]', 'wrongpass');
|
|
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('.error-message')).toContainText('Invalid credentials');
|
|
});
|
|
});
|
|
|
|
// e2e/tags-folders.test.ts
|
|
test.describe('Tags and Folders Management', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Login first
|
|
await page.goto('/login');
|
|
await page.fill('input[name="email"]', 'test@example.com');
|
|
await page.fill('input[name="password"]', 'password123');
|
|
await page.click('button[type="submit"]');
|
|
await expect(page).toHaveURL('/dashboard');
|
|
});
|
|
|
|
test('should create a new tag', async ({ page }) => {
|
|
await page.goto('/dashboard/tags');
|
|
|
|
await page.click('button:has-text("New Tag")');
|
|
await page.fill('input[name="name"]', 'Work Projects');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('text=Work Projects')).toBeVisible();
|
|
});
|
|
|
|
test('should create a new folder', async ({ page }) => {
|
|
await page.goto('/dashboard/folders');
|
|
|
|
await page.click('button:has-text("New Folder")');
|
|
await page.fill('input[name="name"]', 'my-portfolio');
|
|
await page.fill('input[name="display_name"]', 'My Portfolio');
|
|
await page.click('button[type="submit"]');
|
|
|
|
await expect(page.locator('text=My Portfolio')).toBeVisible();
|
|
});
|
|
});
|
|
```
|
|
|
|
### 4. API Contract Tests
|
|
|
|
```typescript
|
|
// tests/api/pocketbase-schema.spec.ts
|
|
import { describe, it, expect } from 'vitest';
|
|
import { pb } from '$lib/pocketbase';
|
|
|
|
describe('PocketBase Schema Validation', () => {
|
|
it('should have correct fields in tags collection', async () => {
|
|
const collection = await pb.collections.getOne('tags');
|
|
|
|
const fields = collection.schema.map((f) => f.name);
|
|
expect(fields).toContain('user_id'); // NOT 'user'
|
|
expect(fields).toContain('name');
|
|
expect(fields).toContain('slug');
|
|
expect(fields).toContain('color');
|
|
expect(fields).toContain('icon');
|
|
});
|
|
|
|
it('should have correct rules for tags collection', async () => {
|
|
const collection = await pb.collections.getOne('tags');
|
|
|
|
// Create rule should only check authentication
|
|
expect(collection.createRule).toBe('@request.auth.id != ""');
|
|
|
|
// Update/Delete should check ownership
|
|
expect(collection.updateRule).toContain('user_id = @request.auth.id');
|
|
expect(collection.deleteRule).toContain('user_id = @request.auth.id');
|
|
});
|
|
});
|
|
```
|
|
|
|
## CI/CD Pipeline Implementation
|
|
|
|
### GitHub Actions Workflow
|
|
|
|
```yaml
|
|
# .github/workflows/test-and-deploy.yml
|
|
name: Test and Deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
|
|
services:
|
|
pocketbase:
|
|
image: ghcr.io/pocketbase/pocketbase:latest
|
|
ports:
|
|
- 8090:8090
|
|
options: >-
|
|
--health-cmd "wget --spider http://localhost:8090/api/health"
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Run lint
|
|
run: npm run lint
|
|
|
|
- name: Run type check
|
|
run: npm run check
|
|
|
|
- name: Setup PocketBase
|
|
run: |
|
|
# Import schema
|
|
curl -X POST http://localhost:8090/api/collections/import \
|
|
-H "Content-Type: application/json" \
|
|
-d @pocketbase-schema.json
|
|
|
|
- name: Run unit tests
|
|
run: npm run test:unit
|
|
|
|
- name: Run integration tests
|
|
env:
|
|
PUBLIC_POCKETBASE_URL: http://localhost:8090
|
|
run: npm run test:integration
|
|
|
|
- name: Run E2E tests
|
|
run: npm run test:e2e
|
|
|
|
- name: Build application
|
|
run: npm run build
|
|
|
|
deploy:
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/main'
|
|
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Deploy to production
|
|
run: |
|
|
# Your deployment script
|
|
echo "Deploying to production..."
|
|
```
|
|
|
|
## Monitoring und Error Tracking
|
|
|
|
### 1. Sentry Integration
|
|
|
|
```typescript
|
|
// src/hooks.client.ts
|
|
import * as Sentry from '@sentry/sveltekit';
|
|
|
|
Sentry.init({
|
|
dsn: import.meta.env.PUBLIC_SENTRY_DSN,
|
|
environment: import.meta.env.MODE,
|
|
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
|
|
tracesSampleRate: 1.0,
|
|
replaysSessionSampleRate: 0.1,
|
|
replaysOnErrorSampleRate: 1.0,
|
|
});
|
|
|
|
export const handleError = Sentry.handleErrorWithSentry();
|
|
```
|
|
|
|
### 2. Health Checks
|
|
|
|
```typescript
|
|
// src/routes/api/health/+server.ts
|
|
import { json } from '@sveltejs/kit';
|
|
import { pb } from '$lib/pocketbase';
|
|
|
|
export async function GET() {
|
|
const checks = {
|
|
app: 'ok',
|
|
database: 'unknown',
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
|
|
try {
|
|
// Test database connection
|
|
await pb.collection('users').getList(1, 1);
|
|
checks.database = 'ok';
|
|
} catch (error) {
|
|
checks.database = 'error';
|
|
}
|
|
|
|
const status = checks.database === 'ok' ? 200 : 503;
|
|
return json(checks, { status });
|
|
}
|
|
```
|
|
|
|
## Pre-Deployment Checklist
|
|
|
|
### Automatisierte Checks
|
|
|
|
- [ ] Alle Unit Tests bestehen
|
|
- [ ] Alle Integration Tests bestehen
|
|
- [ ] Alle E2E Tests bestehen
|
|
- [ ] Keine TypeScript Fehler
|
|
- [ ] Keine Lint-Warnungen
|
|
- [ ] Build erfolgreich
|
|
|
|
### Manuelle Validierung (für kritische Releases)
|
|
|
|
- [ ] Registrierung eines neuen Benutzers
|
|
- [ ] Login/Logout Funktionalität
|
|
- [ ] Tag erstellen/bearbeiten/löschen
|
|
- [ ] Ordner erstellen/bearbeiten/löschen
|
|
- [ ] Link erstellen mit Tags und Ordnern
|
|
- [ ] Stripe Integration (Subscription Flow)
|
|
|
|
## Entwicklungsumgebung Setup
|
|
|
|
### 1. Lokale PocketBase Instanz
|
|
|
|
```bash
|
|
# docker-compose.yml
|
|
version: '3.8'
|
|
services:
|
|
pocketbase-dev:
|
|
image: ghcr.io/pocketbase/pocketbase:latest
|
|
ports:
|
|
- "8090:8090"
|
|
volumes:
|
|
- ./pb_data:/pb_data
|
|
- ./pb_migrations:/pb_migrations
|
|
command: serve --http=0.0.0.0:8090 --dev
|
|
|
|
pocketbase-test:
|
|
image: ghcr.io/pocketbase/pocketbase:latest
|
|
ports:
|
|
- "8091:8090"
|
|
volumes:
|
|
- ./pb_test_data:/pb_data
|
|
command: serve --http=0.0.0.0:8090
|
|
```
|
|
|
|
### 2. Environment Variables
|
|
|
|
```bash
|
|
# .env.development
|
|
PUBLIC_POCKETBASE_URL=http://localhost:8090
|
|
VITE_TEST_MODE=false
|
|
|
|
# .env.test
|
|
PUBLIC_POCKETBASE_URL=http://localhost:8091
|
|
VITE_TEST_MODE=true
|
|
|
|
# .env.production
|
|
PUBLIC_POCKETBASE_URL=https://pb.ulo.ad
|
|
VITE_TEST_MODE=false
|
|
```
|
|
|
|
## Schema Migration Strategy
|
|
|
|
### 1. Versionierte Migrationen
|
|
|
|
```javascript
|
|
// scripts/migrate.js
|
|
import PocketBase from 'pocketbase';
|
|
|
|
const migrations = [
|
|
{
|
|
version: 1,
|
|
name: 'fix_user_id_fields',
|
|
up: async (pb) => {
|
|
// Update tags collection
|
|
const tagsCollection = await pb.collections.getOne('tags');
|
|
tagsCollection.createRule = '@request.auth.id != ""';
|
|
await pb.collections.update('tags', tagsCollection);
|
|
|
|
console.log('✓ Fixed tags collection rules');
|
|
},
|
|
},
|
|
{
|
|
version: 2,
|
|
name: 'add_missing_indexes',
|
|
up: async (pb) => {
|
|
// Add indexes for performance
|
|
// Implementation here
|
|
},
|
|
},
|
|
];
|
|
|
|
async function runMigrations() {
|
|
const pb = new PocketBase(process.env.POCKETBASE_URL);
|
|
await pb.admins.authWithPassword(process.env.PB_ADMIN_EMAIL, process.env.PB_ADMIN_PASSWORD);
|
|
|
|
for (const migration of migrations) {
|
|
console.log(`Running migration ${migration.version}: ${migration.name}`);
|
|
await migration.up(pb);
|
|
}
|
|
}
|
|
|
|
runMigrations().catch(console.error);
|
|
```
|
|
|
|
## Rollback Strategy
|
|
|
|
### 1. Database Backups
|
|
|
|
```bash
|
|
# Tägliche Backups
|
|
0 2 * * * /usr/local/bin/backup-pocketbase.sh
|
|
|
|
# backup-pocketbase.sh
|
|
#!/bin/bash
|
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|
docker exec pocketbase-prod sqlite3 /pb_data/data.db ".backup /backups/backup_$DATE.db"
|
|
# Keep only last 30 days
|
|
find /backups -name "backup_*.db" -mtime +30 -delete
|
|
```
|
|
|
|
### 2. Blue-Green Deployment
|
|
|
|
```nginx
|
|
# nginx.conf
|
|
upstream app_blue {
|
|
server app-blue:3000;
|
|
}
|
|
|
|
upstream app_green {
|
|
server app-green:3000;
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
location / {
|
|
proxy_pass http://app_blue; # Switch to app_green for deployment
|
|
}
|
|
}
|
|
```
|
|
|
|
## Empfohlene Tools
|
|
|
|
### Testing
|
|
|
|
- **Vitest**: Unit und Integration Tests
|
|
- **Playwright**: E2E Tests
|
|
- **MSW**: API Mocking für Tests
|
|
- **Faker.js**: Test-Daten Generation
|
|
|
|
### Monitoring
|
|
|
|
- **Sentry**: Error Tracking
|
|
- **Plausible/Umami**: Privacy-friendly Analytics
|
|
- **Better Stack**: Uptime Monitoring
|
|
- **Grafana + Prometheus**: Metriken
|
|
|
|
### Development
|
|
|
|
- **Husky**: Git Hooks für pre-commit Tests
|
|
- **Commitlint**: Commit Message Validation
|
|
- **Prettier + ESLint**: Code Formatting
|
|
- **TypeScript Strict Mode**: Type Safety
|
|
|
|
## Zeitplan für Implementation
|
|
|
|
### Phase 1 (Sofort - Woche 1)
|
|
|
|
- [ ] Critical Bug Fixes (bereits erledigt)
|
|
- [ ] Basic Health Check Endpoint
|
|
- [ ] Sentry Integration
|
|
|
|
### Phase 2 (Woche 2-3)
|
|
|
|
- [ ] Unit Test Setup mit Vitest
|
|
- [ ] Integration Tests für Auth + CRUD
|
|
- [ ] GitHub Actions Basic Pipeline
|
|
|
|
### Phase 3 (Woche 4-5)
|
|
|
|
- [ ] E2E Tests mit Playwright
|
|
- [ ] Monitoring Dashboard
|
|
- [ ] Automated Backups
|
|
|
|
### Phase 4 (Woche 6+)
|
|
|
|
- [ ] Performance Tests
|
|
- [ ] Load Testing
|
|
- [ ] Advanced CI/CD Features
|
|
|
|
## Kosten-Nutzen-Analyse
|
|
|
|
### Investition
|
|
|
|
- Setup Zeit: ~40-60 Stunden
|
|
- Tools: ~$50-100/Monat (Sentry, Monitoring)
|
|
- CI/CD: GitHub Actions (kostenlos für public repos)
|
|
|
|
### Nutzen
|
|
|
|
- 90% Reduktion von Production Bugs
|
|
- 75% schnellere Bug-Identifikation
|
|
- 50% weniger Downtime
|
|
- Erhöhtes Vertrauen bei Deployments
|
|
- Bessere Developer Experience
|
|
|
|
## Fazit
|
|
|
|
Die implementierte Testing- und Monitoring-Strategie wird die Stabilität von ulo.ad erheblich verbessern. Die wichtigsten Sofortmaßnahmen sind:
|
|
|
|
1. **Integration Tests** für alle kritischen User Flows (Auth, CRUD)
|
|
2. **Schema Validation** Tests für PocketBase
|
|
3. **Automated CI/CD** Pipeline mit Tests vor jedem Deploy
|
|
4. **Error Tracking** mit Sentry
|
|
5. **Health Monitoring** für schnelle Problemerkennung
|
|
|
|
Mit dieser Strategie werden Probleme wie die heute gefundenen (falsche Field Names, inkorrekte Rules) automatisch erkannt, bevor sie in Production gelangen.
|
|
|
|
---
|
|
|
|
_Erstellt am: 14. August 2025_
|
|
_Version: 1.0_
|
|
_Autor: Development Team_
|