feat: Add Directus setup scripts for collections, fields, and relations
- Created setup-directus-collections.js to automate the creation of tech stack collections, fields, and relations in Directus. - Created setup-directus-hobbies.js for setting up hobbies collection with translations. - Created setup-directus-projects.js for establishing projects collection with comprehensive fields and translations. - Added setup-tech-stack-directus.js to populate tech_stack_items with predefined data.
This commit is contained in:
253
docs/DIRECTUS_COLLECTIONS_STRUCTURE.md
Normal file
253
docs/DIRECTUS_COLLECTIONS_STRUCTURE.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Directus Collections Struktur - Vollständige Portfolio Integration
|
||||
|
||||
## 🎯 Übersicht
|
||||
|
||||
Diese Struktur bildet **alles** aus deinem Portfolio in Directus ab, ohne Features zu verlieren.
|
||||
|
||||
## 📦 Collections
|
||||
|
||||
### 1. **tech_stack_categories** (Tech Stack Kategorien)
|
||||
|
||||
**Felder:**
|
||||
- `id` - UUID (Primary Key)
|
||||
- `key` - String (unique) - z.B. "frontend", "backend"
|
||||
- `icon` - String - Icon-Name (z.B. "Globe", "Server")
|
||||
- `sort` - Integer - Reihenfolge der Anzeige
|
||||
- `status` - String (draft/published/archived)
|
||||
- `translations` - O2M zu `tech_stack_categories_translations`
|
||||
|
||||
**Translations (`tech_stack_categories_translations`):**
|
||||
- `id` - UUID
|
||||
- `tech_stack_categories_id` - M2O zu `tech_stack_categories`
|
||||
- `languages_code` - M2O zu `languages` (de-DE, en-US)
|
||||
- `name` - String - z.B. "Frontend & Mobile"
|
||||
|
||||
---
|
||||
|
||||
### 2. **tech_stack_items** (Tech Stack Items)
|
||||
|
||||
**Felder:**
|
||||
- `id` - UUID (Primary Key)
|
||||
- `category_id` - M2O zu `tech_stack_categories`
|
||||
- `name` - String - z.B. "Next.js", "Docker", "Tailwind CSS"
|
||||
- `sort` - Integer - Reihenfolge innerhalb der Kategorie
|
||||
- `url` - String (optional) - Link zur Technologie-Website
|
||||
- `icon_url` - String (optional) - Custom Icon/Logo URL
|
||||
|
||||
**Keine Translations nötig** - Technologie-Namen bleiben gleich in allen Sprachen
|
||||
|
||||
---
|
||||
|
||||
### 3. **projects** (Projekte - Vollständig)
|
||||
|
||||
**Felder:**
|
||||
- `id` - UUID (Primary Key)
|
||||
- `slug` - String (unique) - URL-freundlicher Identifier
|
||||
- `status` - String (draft/published/archived)
|
||||
- `featured` - Boolean - Hervorgehobenes Projekt
|
||||
- `category` - String - z.B. "Web Application", "Mobile App"
|
||||
- `date` - String - Projektzeitraum (z.B. "2024", "2023-2024")
|
||||
- `github` - String (optional) - GitHub Repository URL
|
||||
- `live` - String (optional) - Live Demo URL
|
||||
- `image_url` - String (optional) - Hauptbild des Projekts
|
||||
- `demo_video` - String (optional) - Video URL
|
||||
- `screenshots` - JSON - Array von Screenshot-URLs
|
||||
- `color_scheme` - String - Farbschema des Projekts
|
||||
- `accessibility` - Boolean - Barrierefreiheit vorhanden
|
||||
- `difficulty` - String (Beginner/Intermediate/Advanced/Expert)
|
||||
- `time_to_complete` - String - z.B. "4-6 weeks"
|
||||
- `technologies` - JSON - Array von Technologien
|
||||
- `challenges` - JSON - Array von Herausforderungen
|
||||
- `lessons_learned` - JSON - Array von Learnings
|
||||
- `future_improvements` - JSON - Array von geplanten Verbesserungen
|
||||
- `performance` - JSON - `{"lighthouse": 90, "bundleSize": "50KB", "loadTime": "1.5s"}`
|
||||
- `analytics` - JSON - `{"views": 0, "likes": 0, "shares": 0}` (read-only, kommt aus PostgreSQL)
|
||||
- `sort` - Integer
|
||||
- `date_created` - DateTime
|
||||
- `date_updated` - DateTime
|
||||
- `translations` - O2M zu `projects_translations`
|
||||
|
||||
**Translations (`projects_translations`):**
|
||||
- `id` - UUID
|
||||
- `projects_id` - M2O zu `projects`
|
||||
- `languages_code` - M2O zu `languages`
|
||||
- `title` - String - Projekttitel
|
||||
- `description` - Text - Kurzbeschreibung
|
||||
- `content` - WYSIWYG/Markdown - Vollständiger Projektinhalt
|
||||
- `meta_description` - String - SEO Meta-Description
|
||||
- `keywords` - String - SEO Keywords
|
||||
- `og_image` - String - Open Graph Image URL
|
||||
|
||||
---
|
||||
|
||||
### 4. **content_pages** (Bereits vorhanden, erweitern)
|
||||
|
||||
**Aktuell:**
|
||||
- Für statische Inhalte wie "home-about", "privacy-policy", etc.
|
||||
|
||||
**Erweitern um:**
|
||||
- `key` - Eindeutiger Identifier
|
||||
- `page_type` - String (home_section/legal/about/custom)
|
||||
- `status` - draft/published
|
||||
- `translations` - O2M zu `content_pages_translations`
|
||||
|
||||
---
|
||||
|
||||
### 5. **hobbies** (NEU - für "When I'm Not Coding")
|
||||
|
||||
**Felder:**
|
||||
- `id` - UUID
|
||||
- `key` - String (unique) - z.B. "self_hosting", "gaming"
|
||||
- `icon` - String - Icon-Name
|
||||
- `sort` - Integer
|
||||
- `status` - String
|
||||
- `translations` - O2M zu `hobbies_translations`
|
||||
|
||||
**Translations:**
|
||||
- `id` - UUID
|
||||
- `hobbies_id` - M2O zu `hobbies`
|
||||
- `languages_code` - M2O zu `languages`
|
||||
- `title` - String - z.B. "Self-Hosting & DevOps"
|
||||
- `description` - Text - Beschreibung des Hobbys
|
||||
|
||||
---
|
||||
|
||||
### 6. **messages** (Bereits vorhanden via Directus Native Translations)
|
||||
|
||||
**Struktur:**
|
||||
- Collection: `messages`
|
||||
- Felder:
|
||||
- `key` - String - z.B. "nav.home", "common.loading"
|
||||
- `translations` - Native Directus Translations
|
||||
- `value` - String - Übersetzter Text
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Datenfluss
|
||||
|
||||
### Aktuell (Hybrid):
|
||||
```
|
||||
PostgreSQL (Projects, Analytics) ←→ Next.js ←→ Messages (JSON Files)
|
||||
↓
|
||||
Directus (Content Pages)
|
||||
```
|
||||
|
||||
### Nach Migration (Unified):
|
||||
```
|
||||
Directus (Projects, Tech Stack, Content, Messages, Hobbies)
|
||||
↓
|
||||
GraphQL API
|
||||
↓
|
||||
Next.js (mit Fallback Cache)
|
||||
↓
|
||||
PostgreSQL (nur für Analytics: PageViews, UserInteractions)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Was bleibt in PostgreSQL?
|
||||
|
||||
**Nur echte Analytics-Daten:**
|
||||
- `PageView` - Seitenaufrufe
|
||||
- `UserInteraction` - Likes, Shares, Bookmarks
|
||||
- `Contact` - Kontaktformular-Einträge
|
||||
- `ActivityStatus` - Live-Status (Coding, Gaming, Music)
|
||||
|
||||
**Warum?**
|
||||
- Hohe Frequenz von Updates
|
||||
- Komplexe Aggregations-Queries
|
||||
- Privacy/GDPR (keine Content-vermischung)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Directus UI Benefits
|
||||
|
||||
### Was du gewinnst:
|
||||
1. ✅ **WYSIWYG Editor** für Projekt-Content
|
||||
2. ✅ **Media Library** für Bilder/Screenshots
|
||||
3. ✅ **Bulk Operations** (mehrere Projekte gleichzeitig bearbeiten)
|
||||
4. ✅ **Revision History** (Änderungen nachverfolgen)
|
||||
5. ✅ **Workflows** (Draft → Review → Publish)
|
||||
6. ✅ **Access Control** (verschiedene User-Rollen)
|
||||
7. ✅ **REST + GraphQL API** automatisch generiert
|
||||
8. ✅ **Real-time Updates** via WebSockets
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Migration Plan
|
||||
|
||||
### Phase 1: Tech Stack
|
||||
1. Collections erstellen in Directus
|
||||
2. Daten aus `messages/en.json` & `messages/de.json` migrieren
|
||||
3. `About.tsx` auf Directus umstellen
|
||||
|
||||
### Phase 2: Hobbies
|
||||
1. Collection erstellen
|
||||
2. Daten migrieren
|
||||
3. `About.tsx` erweitern
|
||||
|
||||
### Phase 3: Projects
|
||||
1. Collection mit allen Feldern erstellen
|
||||
2. Migration-Script: PostgreSQL → Directus
|
||||
3. API Routes anpassen (oder Directus direkt nutzen)
|
||||
4. `/manage` Dashboard optional behalten oder durch Directus ersetzen
|
||||
|
||||
### Phase 4: Messages (Optional)
|
||||
1. Alle keys aus `messages/*.json` nach Directus
|
||||
2. `next-intl` Config anpassen für Directus-Loader
|
||||
3. JSON-Files als Fallback behalten
|
||||
|
||||
---
|
||||
|
||||
## 💾 Migration Scripts
|
||||
|
||||
Ich erstelle dir:
|
||||
1. `scripts/migrate-to-directus.ts` - Automatische Migration
|
||||
2. `scripts/sync-from-directus.ts` - Backup zurück zu PostgreSQL
|
||||
3. `lib/directus-extended.ts` - Alle GraphQL Queries
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance
|
||||
|
||||
**Caching-Strategie:**
|
||||
```typescript
|
||||
// 1. Versuch: Directus laden
|
||||
// 2. Fallback: Redis Cache (5min TTL)
|
||||
// 3. Fallback: Static JSON Files
|
||||
// 4. Fallback: Hardcoded Defaults
|
||||
```
|
||||
|
||||
**ISR (Incremental Static Regeneration):**
|
||||
- Projects: Revalidate alle 5 Minuten
|
||||
- Tech Stack: Revalidate alle 1 Stunde
|
||||
- Content Pages: On-Demand Revalidation via Webhook
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security
|
||||
|
||||
**Directus Access:**
|
||||
- Public Read (via Token) für Frontend
|
||||
- Admin Write (via Admin Panel)
|
||||
- Role-based für verschiedene Content-Types
|
||||
|
||||
**Was public bleibt:**
|
||||
- Published Projects
|
||||
- Published Content Pages
|
||||
- Tech Stack
|
||||
- Messages
|
||||
|
||||
**Was protected bleibt:**
|
||||
- Drafts
|
||||
- Analytics
|
||||
- Admin Settings
|
||||
|
||||
---
|
||||
|
||||
## 📝 Nächste Schritte
|
||||
|
||||
Sag mir einfach:
|
||||
1. **"Erstell mir die Collections"** → Ich generiere JSON zum Import in Directus
|
||||
2. **"Bau die Migration"** → Ich schreibe Scripts zum Daten übertragen
|
||||
3. **"Update den Code"** → Ich passe alle Components & APIs an
|
||||
118
docs/DIRECTUS_INTEGRATION_STATUS.md
Normal file
118
docs/DIRECTUS_INTEGRATION_STATUS.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Directus Integration Status
|
||||
|
||||
## ✅ Vollständig integriert
|
||||
|
||||
### Tech Stack
|
||||
- **Collection**: `tech_stack_categories` + `tech_stack_items` ✅
|
||||
- **Data Migration**: 4 Kategorien, ~16 Items (EN + DE) ✅
|
||||
- **API**: `/api/tech-stack` ✅
|
||||
- **Component**: `About.tsx` lädt aus Directus mit Fallback ✅
|
||||
- **Status**: ✅ **PRODUCTION READY**
|
||||
|
||||
### Hobbies
|
||||
- **Collection**: `hobbies` ✅
|
||||
- **Data Migration**: 4 Hobbies (EN + DE) ✅
|
||||
- **API**: `/api/hobbies` ✅
|
||||
- **Component**: `About.tsx` lädt aus Directus mit Fallback ✅
|
||||
- **Status**: ✅ **PRODUCTION READY**
|
||||
|
||||
### Content Pages
|
||||
- **Collection**: Bereits existierend ✅
|
||||
- **Data**: Home-About Page ✅
|
||||
- **API**: `/api/content/page` ✅
|
||||
- **Component**: `About.tsx` lädt aus Directus ✅
|
||||
- **Status**: ✅ **PRODUCTION READY**
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Teilweise integriert
|
||||
|
||||
### Projects
|
||||
- **Collection**: `projects` ✅ (30+ Felder mit Translations)
|
||||
- **Data Migration**: Script vorhanden, PostgreSQL benötigt ⚠️
|
||||
- **API**: `/api/projects` mit **Hybrid-System** ✅
|
||||
- Primär: PostgreSQL (wenn verfügbar)
|
||||
- Fallback: Directus (wenn PostgreSQL offline)
|
||||
- Response enthält `source` field (`postgresql`, `directus`, `directus-empty`, `error`)
|
||||
- **Components**: Verwenden weiterhin `/api/projects` ✅
|
||||
- `Projects.tsx`
|
||||
- `ProjectsPageClient.tsx`
|
||||
- `ProjectCard.tsx`
|
||||
- Admin: `ProjectManager.tsx`
|
||||
- **Status**: ⚠️ **HYBRID MODE** - Funktioniert mit beiden Datenquellen
|
||||
|
||||
**Migration durchführen:**
|
||||
```bash
|
||||
# 1. PostgreSQL starten
|
||||
docker-compose up -d postgres
|
||||
|
||||
# 2. Migration ausführen
|
||||
node scripts/migrate-projects-to-directus.js
|
||||
|
||||
# 3. Optional: PostgreSQL deaktivieren
|
||||
# → /api/projects nutzt automatisch Directus
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Verwendung nach Quelle
|
||||
|
||||
| Content | Source | Load Location |
|
||||
|---------|--------|---------------|
|
||||
| Tech Stack | Directus | `About.tsx` via `/api/tech-stack` |
|
||||
| Hobbies | Directus | `About.tsx` via `/api/hobbies` |
|
||||
| Projects | PostgreSQL → Directus Fallback | `Projects.tsx` via `/api/projects` |
|
||||
| Content Pages | Directus | `About.tsx` via `/api/content/page` |
|
||||
| Messages/i18n | `messages/*.json` | next-intl loader |
|
||||
| Analytics | PostgreSQL | Admin Dashboard |
|
||||
| Users/Auth | PostgreSQL | Admin System |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Hybrid System für Projects
|
||||
|
||||
Die `/api/projects` Route nutzt ein intelligentes Fallback-System:
|
||||
|
||||
1. **PostgreSQL prüfen** via `prisma.$queryRaw`
|
||||
2. **Bei Erfolg**: Daten aus PostgreSQL laden (`source: 'postgresql'`)
|
||||
3. **Bei Fehler**: Automatisch zu Directus wechseln (`source: 'directus'`)
|
||||
4. **Bei beiden offline**: Error Response (`source: 'error'`, Status 503)
|
||||
|
||||
**Vorteile:**
|
||||
- ✅ Zero Downtime bei DB-Migration
|
||||
- ✅ Lokale Entwicklung ohne PostgreSQL möglich
|
||||
- ✅ Bestehende Components funktionieren unverändert
|
||||
- ✅ Graduelle Migration möglich
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Nächste Schritte
|
||||
|
||||
### Option 1: Vollständige Directus-Migration
|
||||
```bash
|
||||
# Projects nach Directus migrieren
|
||||
node scripts/migrate-projects-to-directus.js
|
||||
|
||||
# PostgreSQL optional deaktivieren
|
||||
# → /api/projects nutzt automatisch Directus
|
||||
```
|
||||
|
||||
### Option 2: Hybrid-Betrieb
|
||||
```bash
|
||||
# Nichts tun - System funktioniert bereits!
|
||||
# PostgreSQL = Primary, Directus = Fallback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Zusammenfassung
|
||||
|
||||
| Status | Count | Components |
|
||||
|--------|-------|------------|
|
||||
| ✅ Vollständig in Directus | 3 | Tech Stack, Hobbies, Content Pages |
|
||||
| ⚠️ Hybrid (PostgreSQL + Directus) | 1 | Projects |
|
||||
| ❌ Noch in JSON | 1 | Messages (next-intl) |
|
||||
|
||||
**Ergebnis**: Fast alle User-sichtbaren Inhalte sind bereits über Directus editierbar! 🎉
|
||||
|
||||
**Einzige Ausnahme**: System-Messages (`messages/en.json`, `messages/de.json`) für UI-Texte wie Buttons, Labels, etc.
|
||||
410
docs/DYNAMIC_ACTIVITY_CUSTOM_FIELDS.md
Normal file
410
docs/DYNAMIC_ACTIVITY_CUSTOM_FIELDS.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# 🎛️ 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<string, CustomActivity>;
|
||||
}
|
||||
```
|
||||
|
||||
### 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 (
|
||||
<motion.div key={type} className="custom-activity-card">
|
||||
<h3>{type.charAt(0).toUpperCase() + type.slice(1)}</h3>
|
||||
|
||||
{/* Generic renderer basierend auf Feldern */}
|
||||
{Object.entries(activity).map(([key, value]) => {
|
||||
if (key === 'enabled') return null;
|
||||
|
||||
return (
|
||||
<div key={key}>
|
||||
<span>{key.replace(/_/g, ' ')}: </span>
|
||||
<strong>{value}</strong>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 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<string, any>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<motion.div className="bg-gradient-to-br from-purple-500/10 to-blue-500/5 rounded-xl p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-2xl">{icon}</span>
|
||||
<h3 className="font-bold">{title}</h3>
|
||||
</div>
|
||||
|
||||
{/* Render fields dynamically */}
|
||||
<div className="space-y-1">
|
||||
{Object.entries(data).map(([key, value]) => {
|
||||
if (key === 'enabled') return null;
|
||||
|
||||
// Special handling for specific fields
|
||||
if (key === 'progress' && typeof value === 'number') {
|
||||
return (
|
||||
<div key={key}>
|
||||
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-blue-500 transition-all"
|
||||
style={{ width: `${value}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">{value}%</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Default: key-value pair
|
||||
return (
|
||||
<div key={key} className="text-sm">
|
||||
<span className="text-gray-500">{formatKey(key)}: </span>
|
||||
<span className="font-medium">{formatValue(value)}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
function getIconForType(type: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
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! 🚀**
|
||||
229
docs/DYNAMIC_ACTIVITY_FINAL.md
Normal file
229
docs/DYNAMIC_ACTIVITY_FINAL.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# 🎨 Dynamisches Activity System - Setup
|
||||
|
||||
## ✅ Was jetzt funktioniert:
|
||||
|
||||
**Ohne Code-Änderungen kannst du jetzt beliebige Activities hinzufügen!**
|
||||
|
||||
### n8n sendet:
|
||||
```json
|
||||
{
|
||||
"status": { "text": "online", "color": "green" },
|
||||
"music": { ... },
|
||||
"gaming": { ... },
|
||||
"coding": { ... },
|
||||
"customActivities": {
|
||||
"reading": {
|
||||
"enabled": true,
|
||||
"title": "Clean Architecture",
|
||||
"author": "Robert C. Martin",
|
||||
"progress": 65,
|
||||
"coverUrl": "https://..."
|
||||
},
|
||||
"working_out": {
|
||||
"enabled": true,
|
||||
"activity": "Running",
|
||||
"duration_minutes": 45,
|
||||
"distance_km": 7.2,
|
||||
"calories": 350
|
||||
},
|
||||
"learning": {
|
||||
"enabled": true,
|
||||
"course": "Docker Deep Dive",
|
||||
"platform": "Udemy",
|
||||
"progress": 67
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend rendert automatisch:
|
||||
- ✅ Erkennt alle Activities in `customActivities`
|
||||
- ✅ Generiert Cards mit passenden Farben
|
||||
- ✅ Zeigt Icons (📖 🏃 🎓 📺 etc.)
|
||||
- ✅ Progress Bars für `progress` Felder
|
||||
- ✅ Bilder für `coverUrl`, `image_url`, `albumArt`
|
||||
- ✅ Alle zusätzlichen Felder werden gerendert
|
||||
|
||||
---
|
||||
|
||||
## 🔧 n8n Setup
|
||||
|
||||
### 1. Code Node updaten
|
||||
|
||||
Ersetze den Code in deinem "Code in JavaScript" Node mit:
|
||||
`scripts/n8n-workflow-code-updated.js`
|
||||
|
||||
### 2. Custom Activity hinzufügen
|
||||
|
||||
**Im n8n Code:**
|
||||
```javascript
|
||||
// Nach der Coding Logic, vor dem OUTPUT:
|
||||
customActivities.reading = {
|
||||
enabled: true,
|
||||
title: "Clean Code",
|
||||
author: "Robert C. Martin",
|
||||
progress: 65,
|
||||
coverUrl: "https://covers.openlibrary.org/..."
|
||||
};
|
||||
|
||||
// Oder mehrere:
|
||||
customActivities.working_out = {
|
||||
enabled: true,
|
||||
activity: "Running",
|
||||
duration_minutes: 45
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Automatische Integration (Hardcover Beispiel)
|
||||
|
||||
Bereits im Code enthalten:
|
||||
```javascript
|
||||
if (hardcoverData && hardcoverData.user_book) {
|
||||
const book = hardcoverData.user_book;
|
||||
customActivities.reading = {
|
||||
enabled: true,
|
||||
title: book.book?.title,
|
||||
author: book.book?.contributions?.[0]?.author?.name,
|
||||
progress: book.progress_pages && book.book?.pages
|
||||
? Math.round((book.progress_pages / book.book.pages) * 100)
|
||||
: undefined,
|
||||
coverUrl: book.book?.image_url
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Unterstützte Felder
|
||||
|
||||
Das System erkennt automatisch:
|
||||
|
||||
| Feld | Verwendung |
|
||||
|------|------------|
|
||||
| `enabled` | Zeigt/versteckt die Activity (required!) |
|
||||
| `title`, `name`, `book_title` | Haupttitel (fett) |
|
||||
| `author`, `artist`, `platform` | Untertitel |
|
||||
| `progress` (0-100) | Progress Bar mit Animation |
|
||||
| `progress_label` | Text neben Progress (default: "complete") |
|
||||
| `coverUrl`, `image_url`, `albumArt` | Bild/Cover (40x56px) |
|
||||
| **Alle anderen** | Werden als kleine Text-Zeilen gerendert |
|
||||
|
||||
---
|
||||
|
||||
## 🌈 Verfügbare Typen & Icons
|
||||
|
||||
Vordefinierte Styling:
|
||||
|
||||
| Type | Icon | Farben |
|
||||
|------|------|--------|
|
||||
| `reading` | 📖 | Amber/Orange |
|
||||
| `working_out` | 🏃 | Red/Orange |
|
||||
| `learning` | 🎓 | Purple/Pink |
|
||||
| `streaming` | 📺 | Violet/Purple |
|
||||
| `cooking` | 👨🍳 | Gray (default) |
|
||||
| `traveling` | ✈️ | Gray (default) |
|
||||
| `meditation` | 🧘 | Gray (default) |
|
||||
| `podcast` | 🎙️ | Gray (default) |
|
||||
|
||||
*Alle anderen Typen bekommen Standard-Styling (grau) und ✨ Icon*
|
||||
|
||||
---
|
||||
|
||||
## 📝 Beispiele
|
||||
|
||||
### Reading (mit Cover & Progress)
|
||||
```javascript
|
||||
customActivities.reading = {
|
||||
enabled: true,
|
||||
title: "Clean Architecture",
|
||||
author: "Robert C. Martin",
|
||||
progress: 65,
|
||||
coverUrl: "https://covers.openlibrary.org/b/id/12345-M.jpg"
|
||||
};
|
||||
```
|
||||
|
||||
### Workout (mit Details)
|
||||
```javascript
|
||||
customActivities.working_out = {
|
||||
enabled: true,
|
||||
activity: "Running",
|
||||
duration_minutes: 45,
|
||||
distance_km: 7.2,
|
||||
calories: 350,
|
||||
avg_pace: "6:15 /km"
|
||||
};
|
||||
```
|
||||
|
||||
### Learning (mit Progress)
|
||||
```javascript
|
||||
customActivities.learning = {
|
||||
enabled: true,
|
||||
course: "Docker Deep Dive",
|
||||
platform: "Udemy",
|
||||
instructor: "Nigel Poulton",
|
||||
progress: 67,
|
||||
time_spent_hours: 8.5
|
||||
};
|
||||
```
|
||||
|
||||
### Streaming (Live)
|
||||
```javascript
|
||||
customActivities.streaming = {
|
||||
enabled: true,
|
||||
platform: "Twitch",
|
||||
title: "Building a Portfolio",
|
||||
viewers: 42,
|
||||
url: "https://twitch.tv/yourname"
|
||||
};
|
||||
```
|
||||
|
||||
### Activity deaktivieren
|
||||
```javascript
|
||||
customActivities.reading = {
|
||||
enabled: false // Verschwindet komplett
|
||||
};
|
||||
// Oder einfach nicht hinzufügen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Testing
|
||||
|
||||
**1. n8n Workflow testen:**
|
||||
```bash
|
||||
curl https://your-n8n.com/webhook/denshooter-71242/status
|
||||
```
|
||||
|
||||
**2. Response checken:**
|
||||
```json
|
||||
{
|
||||
"customActivities": {
|
||||
"reading": { "enabled": true, "title": "..." }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. Frontend checken:**
|
||||
- Dev Server: `npm run dev`
|
||||
- Browser: http://localhost:3000
|
||||
- Activity Feed sollte automatisch neue Card zeigen
|
||||
|
||||
**4. Mehrere Activities gleichzeitig:**
|
||||
```javascript
|
||||
customActivities.reading = { enabled: true, ... };
|
||||
customActivities.learning = { enabled: true, ... };
|
||||
customActivities.working_out = { enabled: true, ... };
|
||||
// Alle 3 werden nebeneinander gezeigt (Grid Layout)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✨ Das ist ECHTE Dynamik!
|
||||
|
||||
- ✅ **Keine Code-Änderungen** - Nur n8n Workflow anpassen
|
||||
- ✅ **Keine Deployments** - Änderungen sofort sichtbar
|
||||
- ✅ **Beliebig erweiterbar** - Neue Activity-Typen jederzeit
|
||||
- ✅ **Zero Downtime** - Alles läuft live
|
||||
- ✅ **Responsive** - Grid passt sich automatisch an
|
||||
|
||||
**Genau das was du wolltest!** 🎉
|
||||
165
docs/N8N_READING_INTEGRATION.md
Normal file
165
docs/N8N_READING_INTEGRATION.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# 📚 Reading Activity zu n8n hinzufügen
|
||||
|
||||
## ✅ Was du bereits hast:
|
||||
- ✅ Frontend ist bereit (ActivityFeed.tsx updated)
|
||||
- ✅ TypeScript Interfaces erweitert
|
||||
- ✅ Grid Layout (horizontal auf Desktop, vertikal auf Mobile)
|
||||
- ✅ Conditional Rendering (nur zeigen wenn `isReading: true`)
|
||||
|
||||
## 🔧 n8n Workflow anpassen
|
||||
|
||||
### Option 1: Hardcover Integration (automatisch)
|
||||
|
||||
**1. Neuer Node in n8n: "Hardcover"**
|
||||
```
|
||||
Type: HTTP Request
|
||||
Method: GET
|
||||
URL: https://cms.dk0.dev/api/n8n/hardcover/currently-reading
|
||||
```
|
||||
|
||||
**2. Mit Webhook verbinden**
|
||||
```
|
||||
Webhook → Hardcover (parallel zu Spotify/Lanyard)
|
||||
↓
|
||||
Merge (Node mit 5 Inputs statt 4)
|
||||
↓
|
||||
Code in JavaScript
|
||||
```
|
||||
|
||||
**3. Code Node updaten**
|
||||
Ersetze den gesamten Code in deinem "Code in JavaScript" Node mit dem Code aus:
|
||||
`scripts/n8n-workflow-code-updated.js`
|
||||
|
||||
---
|
||||
|
||||
### Option 2: Manueller Webhook (für Tests)
|
||||
|
||||
**Neuer Workflow: "Set Reading Status"**
|
||||
|
||||
**Node 1: Webhook (POST)**
|
||||
```
|
||||
Path: /set-reading
|
||||
Method: POST
|
||||
```
|
||||
|
||||
**Node 2: PostgreSQL/Set Variable**
|
||||
```javascript
|
||||
// Speichere reading Status in einer Variablen
|
||||
// Oder direkt in Database wenn du willst
|
||||
const { title, author, progress, coverUrl, isReading } = items[0].json.body;
|
||||
|
||||
return [{
|
||||
json: {
|
||||
reading: {
|
||||
isReading: isReading !== false, // default true
|
||||
title,
|
||||
author,
|
||||
progress,
|
||||
coverUrl
|
||||
}
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
curl -X POST https://your-n8n.com/webhook/set-reading \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"isReading": true,
|
||||
"title": "Clean Architecture",
|
||||
"author": "Robert C. Martin",
|
||||
"progress": 65,
|
||||
"coverUrl": "https://example.com/cover.jpg"
|
||||
}'
|
||||
|
||||
# Clear reading:
|
||||
curl -X POST https://your-n8n.com/webhook/set-reading \
|
||||
-d '{"isReading": false}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Wie es aussieht
|
||||
|
||||
### Desktop (breiter Bildschirm):
|
||||
```
|
||||
┌────────────┬────────────┬────────────┬────────────┐
|
||||
│ Coding │ Gaming │ Music │ Reading │
|
||||
│ (RIGHT │ (RIGHT │ │ │
|
||||
│ NOW) │ NOW) │ │ │
|
||||
└────────────┴────────────┴────────────┴────────────┘
|
||||
```
|
||||
|
||||
### Tablet:
|
||||
```
|
||||
┌────────────┬────────────┐
|
||||
│ Coding │ Gaming │
|
||||
└────────────┴────────────┘
|
||||
┌────────────┬────────────┐
|
||||
│ Music │ Reading │
|
||||
└────────────┴────────────┘
|
||||
```
|
||||
|
||||
### Mobile:
|
||||
```
|
||||
┌────────────┐
|
||||
│ Coding │
|
||||
│ (RIGHT │
|
||||
│ NOW) │
|
||||
└────────────┘
|
||||
┌────────────┐
|
||||
│ Gaming │
|
||||
└────────────┘
|
||||
┌────────────┐
|
||||
│ Music │
|
||||
└────────────┘
|
||||
┌────────────┐
|
||||
│ Reading │
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔥 Features
|
||||
|
||||
✅ **Nur zeigen wenn aktiv** - Wenn `isReading: false`, verschwindet die Card komplett
|
||||
✅ **Progress Bar** - Visueller Fortschritt mit Animation
|
||||
✅ **Book Cover** - Kleines Cover (40x56px)
|
||||
✅ **Responsive Grid** - 1 Spalte (Mobile), 2 Spalten (Tablet), 3 Spalten (Desktop)
|
||||
✅ **Smooth Animations** - Fade in/out mit Framer Motion
|
||||
✅ **Amber Theme** - Passt zu "Reading" 📖
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Testing
|
||||
|
||||
**1. Hardcover Endpoint testen:**
|
||||
```bash
|
||||
curl https://cms.dk0.dev/api/n8n/hardcover/currently-reading
|
||||
```
|
||||
|
||||
**2. n8n Webhook testen:**
|
||||
```bash
|
||||
curl https://your-n8n.com/webhook/denshooter-71242/status
|
||||
```
|
||||
|
||||
**3. Frontend testen:**
|
||||
```bash
|
||||
# Dev Server starten
|
||||
npm run dev
|
||||
|
||||
# In Browser Console:
|
||||
fetch('/api/n8n/status').then(r => r.json()).then(console.log)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Nächste Schritte
|
||||
|
||||
1. ✅ Frontend Code ist bereits angepasst
|
||||
2. ⏳ n8n Workflow Code updaten (siehe `scripts/n8n-workflow-code-updated.js`)
|
||||
3. ⏳ Optional: Hardcover Node hinzufügen
|
||||
4. ⏳ Testen und Deploy
|
||||
|
||||
**Alles ready! Nur noch n8n Code austauschen.** 🎉
|
||||
Reference in New Issue
Block a user