# 🎛️ Dynamic Activity System - Custom Fields ohne Deployment ## 🚀 Problem gelöst **Vorher:** - Neue Activity = Schema-Änderung + Code-Update + Deployment - Hardcoded fields wie `reading_book`, `working_out_activity`, etc. **Jetzt:** - Neue Activity = Nur n8n Workflow anpassen - JSON field `custom_activities` für alles - ✅ Zero Downtime - ✅ Kein Deployment nötig --- ## 📊 Schema ```sql ALTER TABLE activity_status ADD COLUMN custom_activities JSONB DEFAULT '{}'; ``` **Struktur:** ```json { "reading": { "enabled": true, "book_title": "Clean Code", "author": "Robert C. Martin", "progress": 65, "platform": "hardcover", "cover_url": "https://..." }, "working_out": { "enabled": true, "activity": "Running", "duration_minutes": 45, "calories": 350 }, "learning": { "enabled": true, "course": "Docker Deep Dive", "platform": "Udemy", "progress": 23 }, "streaming": { "enabled": true, "platform": "Twitch", "viewers": 42, "game": "Minecraft" } } ``` --- ## 🔧 n8n Workflow Beispiel ### Workflow: "Update Custom Activity" **Node 1: Webhook (POST)** ``` URL: /webhook/custom-activity Method: POST Body: { "type": "reading", "data": { "enabled": true, "book_title": "Clean Code", "author": "Robert C. Martin", "progress": 65 } } ``` **Node 2: Function - Build JSON** ```javascript const { type, data } = items[0].json; return [{ json: { type, data, query: ` UPDATE activity_status SET custom_activities = jsonb_set( COALESCE(custom_activities, '{}'::jsonb), '{${type}}', $1::jsonb ), updated_at = NOW() WHERE id = 1 `, params: [JSON.stringify(data)] } }]; ``` **Node 3: PostgreSQL** - Query: `={{$json.query}}` - Parameters: `={{$json.params}}` --- ## 🎨 Frontend Integration ### TypeScript Interface ```typescript interface CustomActivity { enabled: boolean; [key: string]: any; // Dynamisch! } interface StatusData { // ... existing fields customActivities?: Record; } ``` ### API Route Update ```typescript // app/api/n8n/status/route.ts export async function GET() { const statusData = await fetch(n8nWebhookUrl); return NextResponse.json({ // ... existing fields customActivities: statusData.custom_activities || {} }); } ``` ### Component Rendering ```tsx // app/components/ActivityFeed.tsx {Object.entries(data.customActivities || {}).map(([type, activity]) => { if (!activity.enabled) return null; return (

{type.charAt(0).toUpperCase() + type.slice(1)}

{/* Generic renderer basierend auf Feldern */} {Object.entries(activity).map(([key, value]) => { if (key === 'enabled') return null; return (
{key.replace(/_/g, ' ')}: {value}
); })}
); })} ``` --- ## 📱 Beispiele ### 1. Reading Activity (Hardcover Integration) **n8n Workflow:** ``` Hardcover API → Get Currently Reading → Update Database ``` **Webhook Body:** ```json { "type": "reading", "data": { "enabled": true, "book_title": "Clean Architecture", "author": "Robert C. Martin", "progress": 45, "platform": "hardcover", "cover_url": "https://covers.openlibrary.org/...", "started_at": "2025-01-20" } } ``` **Frontend zeigt:** ``` 📖 Reading Clean Architecture by Robert C. Martin Progress: 45% [Progress Bar] ``` --- ### 2. Workout Activity (Strava/Apple Health) **Webhook Body:** ```json { "type": "working_out", "data": { "enabled": true, "activity": "Running", "duration_minutes": 45, "distance_km": 7.2, "calories": 350, "avg_pace": "6:15 /km", "started_at": "2025-01-23T06:30:00Z" } } ``` **Frontend zeigt:** ``` 🏃 Working Out Running - 7.2 km in 45 minutes 350 calories burned ``` --- ### 3. Learning Activity (Udemy/Coursera) **Webhook Body:** ```json { "type": "learning", "data": { "enabled": true, "course": "Docker Deep Dive", "platform": "Udemy", "instructor": "Nigel Poulton", "progress": 67, "time_spent_hours": 8.5 } } ``` **Frontend zeigt:** ``` 🎓 Learning Docker Deep Dive on Udemy Progress: 67% (8.5 hours) ``` --- ### 4. Live Streaming **Webhook Body:** ```json { "type": "streaming", "data": { "enabled": true, "platform": "Twitch", "title": "Building a Portfolio with Next.js", "viewers": 42, "game": "Software Development", "url": "https://twitch.tv/yourname" } } ``` **Frontend zeigt:** ``` 📺 LIVE on Twitch Building a Portfolio with Next.js 👥 42 viewers [Watch Stream →] ``` --- ## 🔥 Clear Activity **Webhook zum Deaktivieren:** ```bash curl -X POST https://n8n.example.com/webhook/custom-activity \ -H "Content-Type: application/json" \ -d '{ "type": "reading", "data": { "enabled": false } }' ``` **Alle Custom Activities clearen:** ```sql UPDATE activity_status SET custom_activities = '{}'::jsonb WHERE id = 1; ``` --- ## 🎯 Vorteile | Feature | Vorher | Nachher | |---------|--------|---------| | **Neue Activity** | Schema + Code + Deploy | Nur n8n Workflow | | **Activity entfernen** | Schema + Code + Deploy | Webhook mit `enabled: false` | | **Deployment** | Ja | Nein | | **Downtime** | Ja | Nein | | **Flexibilität** | Starr | Komplett dynamisch | --- ## 🚀 Migration ```bash # 1. Schema erweitern psql -d portfolio_dev -f prisma/migrations/add_custom_activities.sql # 2. Prisma Schema updaten # prisma/schema.prisma # customActivities Json? @map("custom_activities") # 3. Prisma Generate npx prisma generate # 4. Fertig! Keine weiteren Code-Änderungen nötig ``` --- ## 🎨 Smart Renderer Component ```tsx // components/CustomActivityCard.tsx interface CustomActivityCardProps { type: string; data: Record; } export function CustomActivityCard({ type, data }: CustomActivityCardProps) { const icon = getIconForType(type); // Mapping: reading → 📖, working_out → 🏃 const title = type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); return (
{icon}

{title}

{/* Render fields dynamically */}
{Object.entries(data).map(([key, value]) => { if (key === 'enabled') return null; // Special handling for specific fields if (key === 'progress' && typeof value === 'number') { return (
{value}%
); } // Default: key-value pair return (
{formatKey(key)}: {formatValue(value)}
); })}
); } function getIconForType(type: string): string { const icons: Record = { reading: '📖', working_out: '🏃', learning: '🎓', streaming: '📺', cooking: '👨‍🍳', traveling: '✈️', }; return icons[type] || '✨'; } ``` --- ## 🎯 Zusammenfassung Mit dem `custom_activities` JSONB Field kannst du: - ✅ Beliebig viele Activity-Typen hinzufügen - ✅ Ohne Schema-Änderungen - ✅ Ohne Code-Deployments - ✅ Nur über n8n Webhooks steuern - ✅ Frontend rendert automatisch alles **Das ist TRUE DYNAMIC! 🚀**