Files
portfolio/docs/DYNAMIC_ACTIVITY_MANAGEMENT.md
2026-01-07 14:30:00 +01:00

470 lines
15 KiB
Markdown

# 🎛️ Dynamic Activity Management - No Rebuild Required!
## Übersicht
Dieses System erlaubt dir, alle Aktivitäten dynamisch zu steuern **ohne die Website neu zu bauen**. Alle Änderungen werden in Echtzeit über die Datenbank und n8n gesteuert.
---
## 🎯 Konzept: Zentrales Management
```
┌─────────────────┐
│ n8n Dashboard │ ← Du steuerst hier alles
└────────┬────────┘
┌─────────────────┐
│ PostgreSQL │ ← Daten werden hier gespeichert
└────────┬────────┘
┌─────────────────┐
│ API Route │ ← Website liest alle 30s
└────────┬────────┘
┌─────────────────┐
│ ActivityFeed UI │ ← Besucher sehen live updates
└─────────────────┘
```
**Vorteile:**
- ✅ Keine Website-Rebuild notwendig
- ✅ Echtzeit-Updates (30 Sekunden)
- ✅ Volle Kontrolle via n8n
- ✅ Historische Daten verfügbar
- ✅ Multiple Steuerungsmöglichkeiten
---
## 🎮 Management Optionen
### Option 1: n8n Dashboard UI ⭐ EMPFOHLEN
Erstelle ein simples n8n Workflow-Dashboard mit Webhook-Buttons:
**Workflow: "Activity Manager Dashboard"**
```json
{
"nodes": [
{
"name": "HTTP Server",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "activity-dashboard",
"method": "GET",
"responseMode": "responseNode",
"options": {}
}
},
{
"name": "HTML Dashboard",
"type": "n8n-nodes-base.respondToWebhook",
"parameters": {
"responseBody": "=<html>\n<head>\n <title>Activity Manager</title>\n <style>\n body { font-family: system-ui; max-width: 800px; margin: 50px auto; padding: 20px; }\n .activity-section { background: #f5f5f5; padding: 20px; margin: 20px 0; border-radius: 8px; }\n button { background: #333; color: white; padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; cursor: pointer; }\n button:hover { background: #555; }\n input, select, textarea { padding: 8px; margin: 5px 0; border: 1px solid #ddd; border-radius: 4px; width: 100%; }\n .status { display: inline-block; width: 12px; height: 12px; border-radius: 50%; }\n .active { background: #4ade80; }\n .inactive { background: #ef4444; }\n </style>\n</head>\n<body>\n <h1>🎛️ Activity Manager</h1>\n \n <div class=\"activity-section\">\n <h2>🎵 Music Control</h2>\n <p>Status: <span class=\"status active\"></span> Auto-syncing from Spotify</p>\n <button onclick=\"fetch('/webhook/stop-music', {method:'POST'})\">Stop Music Display</button>\n </div>\n\n <div class=\"activity-section\">\n <h2>💻 Coding Activity</h2>\n <input type=\"text\" id=\"project\" placeholder=\"Project name\">\n <input type=\"text\" id=\"language\" placeholder=\"Language (e.g., TypeScript)\">\n <input type=\"text\" id=\"repo\" placeholder=\"GitHub Repo URL\">\n <button onclick=\"updateCoding()\">Update Coding Status</button>\n <button onclick=\"clearCoding()\">Clear</button>\n </div>\n\n <div class=\"activity-section\">\n <h2>🎮 Gaming</h2>\n <input type=\"text\" id=\"game\" placeholder=\"Game name\">\n <select id=\"platform\">\n <option>steam</option>\n <option>playstation</option>\n <option>xbox</option>\n </select>\n <button onclick=\"updateGaming()\">Start Gaming</button>\n <button onclick=\"stopGaming()\">Stop Gaming</button>\n </div>\n\n <div class=\"activity-section\">\n <h2>😊 Mood & Status</h2>\n <input type=\"text\" id=\"mood\" placeholder=\"Emoji (e.g., 😊, 💻, 🎮)\" maxlength=\"2\">\n <textarea id=\"message\" placeholder=\"Custom message\" rows=\"2\"></textarea>\n <button onclick=\"updateStatus()\">Update Status</button>\n </div>\n\n <div class=\"activity-section\">\n <h2>🏃 Manual Activities</h2>\n <select id=\"activity-type\">\n <option value=\"running\">Running</option>\n <option value=\"reading\">Reading</option>\n <option value=\"watching\">Watching</option>\n </select>\n <input type=\"text\" id=\"activity-details\" placeholder=\"Details\">\n <button onclick=\"updateActivity()\">Start Activity</button>\n <button onclick=\"clearActivity()\">Clear</button>\n </div>\n\n <div class=\"activity-section\">\n <h2>🧹 Quick Actions</h2>\n <button onclick=\"clearAll()\">Clear All Activities</button>\n <button onclick=\"setAFK()\">Set AFK</button>\n <button onclick=\"setFocusMode()\">Focus Mode (DND)</button>\n </div>\n\n <script>\n function updateCoding() {\n fetch('/webhook/update-activity', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n type: 'coding',\n project: document.getElementById('project').value,\n language: document.getElementById('language').value,\n repo: document.getElementById('repo').value\n })\n }).then(() => alert('✅ Updated!'));\n }\n\n function updateGaming() {\n fetch('/webhook/update-activity', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n type: 'gaming',\n game: document.getElementById('game').value,\n platform: document.getElementById('platform').value\n })\n }).then(() => alert('✅ Gaming status updated!'));\n }\n\n function updateStatus() {\n fetch('/webhook/update-status', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n mood: document.getElementById('mood').value,\n message: document.getElementById('message').value\n })\n }).then(() => alert('✅ Status updated!'));\n }\n\n function clearAll() {\n if(confirm('Clear all activities?')) {\n fetch('/webhook/clear-all', {method: 'POST'})\n .then(() => alert('✅ All cleared!'));\n }\n }\n\n function setAFK() {\n fetch('/webhook/update-status', {\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({mood: '💤', message: 'AFK - Be right back'})\n }).then(() => alert('✅ AFK mode activated!'));\n }\n </script>\n</body>\n</html>"
}
}
]
}
```
**Zugriff:**
```
https://your-n8n-instance.com/webhook/activity-dashboard
```
---
### Option 2: Discord Bot Commands
Erstelle einen Discord Bot für schnelle Updates:
**Commands:**
```
!status 💻 Working on new features
!coding Portfolio Next.js
!music <automatic from spotify>
!gaming Elden Ring
!clear
!afk
```
**n8n Workflow:**
```json
{
"nodes": [
{
"name": "Discord Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "discord-bot"
}
},
{
"name": "Parse Command",
"type": "n8n-nodes-base.function",
"parameters": {
"functionCode": "const message = items[0].json.content;\nconst [command, ...args] = message.split(' ');\n\nswitch(command) {\n case '!status':\n return [{\n json: {\n action: 'update_status',\n mood: args[0],\n message: args.slice(1).join(' ')\n }\n }];\n \n case '!coding':\n return [{\n json: {\n action: 'update_activity',\n type: 'coding',\n details: args.join(' ')\n }\n }];\n \n case '!clear':\n return [{\n json: { action: 'clear_all' }\n }];\n}\n\nreturn [{ json: {} }];"
}
},
{
"name": "Update Database",
"type": "n8n-nodes-base.postgres"
}
]
}
```
---
### Option 3: Mobile App / Shortcut
**iOS Shortcuts:**
```
1. "Start Coding" → POST to n8n webhook
2. "Finished Work" → Clear activity
3. "Set Mood" → Update status
```
**Android Tasker:**
- Similar webhooks
- Location-based triggers
- Time-based automation
---
### Option 4: CLI Tool
Erstelle ein simples CLI Tool:
```bash
#!/bin/bash
# activity.sh
N8N_URL="https://your-n8n-instance.com"
case "$1" in
status)
curl -X POST "$N8N_URL/webhook/update-status" \
-H "Content-Type: application/json" \
-d "{\"mood\":\"$2\",\"message\":\"$3\"}"
;;
coding)
curl -X POST "$N8N_URL/webhook/update-activity" \
-H "Content-Type: application/json" \
-d "{\"type\":\"coding\",\"project\":\"$2\",\"language\":\"$3\"}"
;;
clear)
curl -X POST "$N8N_URL/webhook/clear-all"
;;
*)
echo "Usage: activity.sh [status|coding|clear] [args]"
;;
esac
```
**Usage:**
```bash
./activity.sh status 💻 "Deep work mode"
./activity.sh coding "Portfolio" "TypeScript"
./activity.sh clear
```
---
## 🔄 Automatische Sync-Workflows
### Musik geht weg wenn nicht mehr läuft
**n8n Workflow: "Spotify Auto-Clear"**
```json
{
"nodes": [
{
"name": "Check Every 30s",
"type": "n8n-nodes-base.cron",
"parameters": {
"cronExpression": "*/30 * * * * *"
}
},
{
"name": "Get Spotify Status",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.spotify.com/v1/me/player/currently-playing"
}
},
{
"name": "Check If Playing",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.is_playing}}",
"value2": false
}
]
}
}
},
{
"name": "Clear Music from Database",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "UPDATE activity_status SET music_playing = FALSE, music_track = NULL, music_artist = NULL, music_album = NULL, music_album_art = NULL, music_progress = NULL WHERE id = 1"
}
}
]
}
```
### Auto-Clear nach Zeit
**n8n Workflow: "Activity Timeout"**
```javascript
// Function Node: Check Activity Age
const lastUpdate = new Date(items[0].json.updated_at);
const now = new Date();
const hoursSinceUpdate = (now - lastUpdate) / (1000 * 60 * 60);
// Clear activity if older than 2 hours
if (hoursSinceUpdate > 2) {
return [{
json: {
should_clear: true,
reason: `Activity too old (${hoursSinceUpdate.toFixed(1)} hours)`
}
}];
}
return [{ json: { should_clear: false } }];
```
### Smart Activity Detection
**Workflow: "Detect Coding from Git Commits"**
```javascript
// When you push to GitHub
const commit = items[0].json;
const repo = commit.repository.name;
const message = commit.head_commit.message;
// Detect language from files
const files = commit.head_commit.modified;
const language = files[0]?.split('.').pop(); // Get extension
return [{
json: {
activity_type: 'coding',
activity_details: message,
activity_project: repo,
activity_language: language,
activity_repo: commit.repository.html_url,
link: commit.head_commit.url
}
}];
```
---
## 📊 Activity Analytics Dashboard
**Workflow: "Activity History & Stats"**
Speichere Historie in separater Tabelle:
```sql
CREATE TABLE activity_history (
id SERIAL PRIMARY KEY,
activity_type VARCHAR(50),
details TEXT,
duration INTEGER, -- in minutes
started_at TIMESTAMP,
ended_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
-- View für Statistiken
CREATE VIEW activity_stats AS
SELECT
activity_type,
COUNT(*) as count,
SUM(duration) as total_minutes,
AVG(duration) as avg_duration,
DATE(created_at) as date
FROM activity_history
GROUP BY activity_type, DATE(created_at)
ORDER BY date DESC;
```
**Dashboard Queries:**
```sql
-- Heute
SELECT * FROM activity_stats WHERE date = CURRENT_DATE;
-- Diese Woche
SELECT activity_type, SUM(total_minutes) as minutes
FROM activity_stats
WHERE date >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY activity_type;
-- Most Coded Languages
SELECT activity_language, COUNT(*)
FROM activity_history
WHERE activity_type = 'coding'
GROUP BY activity_language
ORDER BY COUNT(*) DESC;
```
---
## 🎨 Custom Activity Types
Erweitere das System mit eigenen Activity-Types:
```sql
-- Add custom columns
ALTER TABLE activity_status
ADD COLUMN custom_activity_type VARCHAR(100),
ADD COLUMN custom_activity_data JSONB;
-- Example: Workout tracking
UPDATE activity_status SET
custom_activity_type = 'workout',
custom_activity_data = '{
"exercise": "Push-ups",
"reps": 50,
"icon": "💪",
"color": "orange"
}'::jsonb
WHERE id = 1;
```
**Frontend Support:**
```typescript
// In ActivityFeed.tsx
interface CustomActivity {
type: string;
data: {
icon: string;
color: string;
[key: string]: any;
};
}
// Render custom activities dynamically
if (data.customActivity) {
return (
<motion.div
className={`bg-${data.customActivity.data.color}/20`}
>
<span>{data.customActivity.data.icon}</span>
<span>{data.customActivity.type}</span>
{/* Render data fields dynamically */}
</motion.div>
);
}
```
---
## 🔐 Security & Best Practices
### 1. Webhook Authentication
```javascript
// In n8n webhook
const secret = $credentials.webhookSecret;
const providedSecret = $node["Webhook"].json.headers["x-webhook-secret"];
if (secret !== providedSecret) {
return [{
json: { error: "Unauthorized" },
statusCode: 401
}];
}
```
### 2. Rate Limiting
```sql
-- Track requests
CREATE TABLE webhook_requests (
ip_address VARCHAR(45),
endpoint VARCHAR(100),
requested_at TIMESTAMP DEFAULT NOW()
);
-- Check rate limit (max 10 requests per minute)
SELECT COUNT(*) FROM webhook_requests
WHERE ip_address = $1
AND requested_at > NOW() - INTERVAL '1 minute';
```
### 3. Input Validation
```javascript
// In n8n Function node
const validateInput = (data) => {
if (!data.type || typeof data.type !== 'string') {
throw new Error('Invalid activity type');
}
if (data.type === 'coding' && !data.project) {
throw new Error('Project name required for coding activity');
}
return true;
};
```
---
## 🚀 Quick Deploy Checklist
- [ ] Datenbank Table erstellt (`setup_activity_status.sql`)
- [ ] n8n Workflows importiert
- [ ] Spotify OAuth konfiguriert
- [ ] GitHub Webhooks eingerichtet
- [ ] Dashboard-URL getestet
- [ ] API Routes deployed
- [ ] Environment Variables gesetzt
- [ ] Frontend ActivityFeed getestet
- [ ] Auto-Clear Workflows aktiviert
---
## 💡 Pro-Tipps
1. **Backup System**: Exportiere n8n Workflows regelmäßig
2. **Monitoring**: Setup alerts wenn Workflows fehlschlagen
3. **Testing**: Nutze n8n's Test-Modus vor Produktion
4. **Logging**: Speichere alle Aktivitäten für Analyse
5. **Fallbacks**: Zeige Placeholder wenn keine Daten vorhanden
---
## 📞 Quick Support Commands
```bash
# Check database status
psql -d portfolio_dev -c "SELECT * FROM activity_status WHERE id = 1;"
# Clear all activities
psql -d portfolio_dev -c "UPDATE activity_status SET activity_type = NULL, music_playing = FALSE WHERE id = 1;"
# View recent history
psql -d portfolio_dev -c "SELECT * FROM activity_history ORDER BY created_at DESC LIMIT 10;"
# Test n8n webhook
curl -X POST https://your-n8n.com/webhook/update-activity \
-H "Content-Type: application/json" \
-d '{"type":"coding","details":"Testing","project":"Portfolio"}'
```
---
Happy automating! 🎉