mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
📝 docs(devlog): add devlogs for Feb 12 and Feb 13
- Feb 12: GDPR Self-Service, Matrix Mobile UX, Mac Mini Stability - Feb 13: Gift Codes, Stripe Integration, Zitare Deployment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
284cd004aa
commit
40a3c89852
2 changed files with 1145 additions and 0 deletions
|
|
@ -0,0 +1,426 @@
|
|||
---
|
||||
title: 'GDPR Self-Service, Matrix Mobile UX & Mac Mini Stability'
|
||||
description: 'Neue GDPR Self-Service Endpoints für Nutzer-Daten, Matrix Web Mobile-Navigation mit FAB und Room Restoration, Mac Mini Stability-Improvements mit Health Checks und Container Recovery'
|
||||
date: 2026-02-12
|
||||
author: 'Till Schneider'
|
||||
category: 'feature'
|
||||
tags:
|
||||
[
|
||||
'gdpr',
|
||||
'dsgvo',
|
||||
'matrix',
|
||||
'mobile',
|
||||
'mac-mini',
|
||||
'stability',
|
||||
'monitoring',
|
||||
'docker',
|
||||
'admin',
|
||||
'health-checks',
|
||||
]
|
||||
featured: true
|
||||
commits: 22
|
||||
readTime: 10
|
||||
stats:
|
||||
filesChanged: 102
|
||||
linesAdded: 9395
|
||||
linesRemoved: 126
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 22
|
||||
workingHours:
|
||||
start: '2026-02-12T11:00'
|
||||
end: '2026-02-13T11:00'
|
||||
---
|
||||
|
||||
**22 Commits** mit Fokus auf DSGVO-Compliance, Mobile UX und Server-Stabilität:
|
||||
|
||||
- **GDPR Self-Service** - Neue Endpoints für User Data Export
|
||||
- **Matrix Mobile UX** - FAB für Sidebar, Room Restoration
|
||||
- **Mac Mini Stability** - Health Checks, Container Recovery, LaunchD Fixes
|
||||
- **Monitoring** - Alerting Stack mit Maintenance Scripts
|
||||
- **Admin API Fixes** - Controller Route Prefix Korrekturen
|
||||
|
||||
---
|
||||
|
||||
## GDPR Self-Service Endpoints
|
||||
|
||||
Neue Self-Service Endpoints für Nutzer, um ihre Daten einzusehen und zu exportieren.
|
||||
|
||||
### Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ GDPR Data Aggregation │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ User Request │ │
|
||||
│ │ /me/data │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ ┌─────────────────────────────────────┐│
|
||||
│ │ mana-core-auth │────>│ Backend Services (parallel fetch) ││
|
||||
│ │ │ │ ││
|
||||
│ │ /me/data │ │ Calendar │ Todo │ Contacts │ ... ││
|
||||
│ └─────────────────┘ └─────────────────────────────────────┘│
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Aggregated │ │
|
||||
│ │ User Data │ │
|
||||
│ │ (JSON/ZIP) │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Neue Endpoints
|
||||
|
||||
```typescript
|
||||
// mana-core-auth: Self-Service Data
|
||||
GET /me/data // Aggregierte Daten aller Services
|
||||
GET /me/data/export // ZIP-Download für DSGVO-Auskunft
|
||||
DELETE /me/data // Account und alle Daten löschen (Art. 17)
|
||||
|
||||
// Backend Services: GDPR Endpoints
|
||||
GET /admin/gdpr/users/:userId/data
|
||||
DELETE /admin/gdpr/users/:userId/data
|
||||
```
|
||||
|
||||
### Implementierung
|
||||
|
||||
```typescript
|
||||
// auth: me.controller.ts
|
||||
@Get('data')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async getUserData(@CurrentUser() user: CurrentUserData) {
|
||||
const services = ['calendar', 'todo', 'contacts', 'photos', 'clock', 'storage'];
|
||||
|
||||
const dataPromises = services.map(async (service) => {
|
||||
const url = this.configService.get(`${service.toUpperCase()}_BACKEND_URL`);
|
||||
return this.fetchServiceData(url, user.userId);
|
||||
});
|
||||
|
||||
const results = await Promise.allSettled(dataPromises);
|
||||
return this.aggregateResults(results, services);
|
||||
}
|
||||
```
|
||||
|
||||
### Backend Integration
|
||||
|
||||
Neue GDPR Endpoints zu Photos, Clock und Storage Backends hinzugefügt:
|
||||
|
||||
| Backend | Endpoint | Daten |
|
||||
| ------- | ---------------------------- | ------------------ |
|
||||
| Photos | `/admin/gdpr/users/:id/data` | Fotos, Alben, EXIF |
|
||||
| Clock | `/admin/gdpr/users/:id/data` | Timer, Sessions |
|
||||
| Storage | `/admin/gdpr/users/:id/data` | Dateien, Ordner |
|
||||
|
||||
---
|
||||
|
||||
## Matrix Web Mobile UX
|
||||
|
||||
Verbesserte Mobile-Navigation für die Matrix PWA.
|
||||
|
||||
### FAB für Sidebar
|
||||
|
||||
```svelte
|
||||
<!-- FloatingActionButton für Mobile Sidebar -->
|
||||
<script lang="ts">
|
||||
let { onOpenSidebar } = $props();
|
||||
</script>
|
||||
|
||||
<button class="fab fixed bottom-20 right-4 z-50 md:hidden" onclick={onOpenSidebar}>
|
||||
<MenuIcon />
|
||||
</button>
|
||||
```
|
||||
|
||||
### Room Restoration
|
||||
|
||||
Automatische Wiederherstellung des zuletzt ausgewählten Chats:
|
||||
|
||||
```typescript
|
||||
// Beim App-Start: Letzten Room wiederherstellen
|
||||
onMount(() => {
|
||||
const lastRoomId = localStorage.getItem('matrix:lastRoom');
|
||||
if (lastRoomId && rooms.find((r) => r.roomId === lastRoomId)) {
|
||||
selectRoom(lastRoomId);
|
||||
}
|
||||
});
|
||||
|
||||
// Bei Room-Wechsel: Speichern
|
||||
function selectRoom(roomId: string) {
|
||||
currentRoomId = roomId;
|
||||
localStorage.setItem('matrix:lastRoom', roomId);
|
||||
}
|
||||
```
|
||||
|
||||
### Message Interface Fix
|
||||
|
||||
Fehlende Props zur Message.svelte Interface hinzugefügt:
|
||||
|
||||
```typescript
|
||||
interface MessageProps {
|
||||
message: MatrixMessage;
|
||||
isOwn: boolean;
|
||||
showAvatar: boolean;
|
||||
// Neu hinzugefügt:
|
||||
onReply?: (msg: MatrixMessage) => void;
|
||||
onReact?: (msg: MatrixMessage, emoji: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mac Mini Stability Improvements
|
||||
|
||||
Umfangreiche Verbesserungen für die Server-Stabilität.
|
||||
|
||||
### Health Check Updates
|
||||
|
||||
```yaml
|
||||
# docker-compose.macmini.yml
|
||||
services:
|
||||
mana-core-auth:
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3001/health']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
zitare-backend:
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3007/health']
|
||||
# Korrigierter Pfad (vorher /api/health)
|
||||
```
|
||||
|
||||
### Container Recovery Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/mac-mini/container-recovery.sh
|
||||
|
||||
UNHEALTHY=$(docker ps --filter "health=unhealthy" --format "{{.Names}}")
|
||||
|
||||
for container in $UNHEALTHY; do
|
||||
echo "Restarting unhealthy container: $container"
|
||||
docker restart "$container"
|
||||
|
||||
# Warte auf Health Check
|
||||
sleep 30
|
||||
|
||||
# Prüfe Status
|
||||
STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$container")
|
||||
if [ "$STATUS" != "healthy" ]; then
|
||||
echo "WARNING: $container still unhealthy after restart"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
### LaunchD Plist Fix
|
||||
|
||||
```xml
|
||||
<!-- com.mana.container-recovery.plist -->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.mana.container-recovery</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Users/till/projects/manacore-monorepo/scripts/mac-mini/container-recovery.sh</string>
|
||||
</array>
|
||||
<key>StartInterval</key>
|
||||
<integer>300</integer> <!-- Alle 5 Minuten -->
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/mana/container-recovery.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
### Disabled Services
|
||||
|
||||
Temporär deaktivierte Services (fehlende Deployments):
|
||||
|
||||
```yaml
|
||||
# Auskommentiert bis Deployment fertig
|
||||
# inventory-backend:
|
||||
# nutriphi-backend:
|
||||
# wisekeep-backend:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring: Alerting Stack
|
||||
|
||||
Neues Alerting-System mit Prometheus und Discord Notifications.
|
||||
|
||||
### Alert Rules
|
||||
|
||||
```yaml
|
||||
# prometheus/alerts.yml
|
||||
groups:
|
||||
- name: service_alerts
|
||||
rules:
|
||||
- alert: ServiceDown
|
||||
expr: up == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: '{{ $labels.job }} is down'
|
||||
|
||||
- alert: HighMemoryUsage
|
||||
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 0.9
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: 'Memory usage above 90%'
|
||||
|
||||
- alert: DiskSpaceLow
|
||||
expr: node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.1
|
||||
for: 10m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: 'Disk space below 10%'
|
||||
```
|
||||
|
||||
### Maintenance Scripts
|
||||
|
||||
```bash
|
||||
# scripts/mac-mini/maintenance.sh
|
||||
|
||||
# 1. Docker Cleanup
|
||||
docker system prune -f --volumes
|
||||
|
||||
# 2. Log Rotation
|
||||
find /var/log/mana -name "*.log" -mtime +7 -delete
|
||||
|
||||
# 3. Health Report
|
||||
./scripts/mac-mini/health-report.sh | tee /var/log/mana/daily-health.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin API Fixes
|
||||
|
||||
Korrekturen für Controller Route Prefixes.
|
||||
|
||||
### Problem
|
||||
|
||||
Doppelte API-Prefixes führten zu 404-Fehlern:
|
||||
|
||||
```
|
||||
GET /api/v1/api/v1/admin/users → 404
|
||||
```
|
||||
|
||||
### Lösung
|
||||
|
||||
```typescript
|
||||
// VORHER (falsch)
|
||||
@Controller('api/v1/admin') // + Global Prefix = /api/v1/api/v1/admin
|
||||
export class AdminController {}
|
||||
|
||||
// NACHHER (korrekt)
|
||||
@Controller('admin') // + Global Prefix = /api/v1/admin
|
||||
export class AdminController {}
|
||||
```
|
||||
|
||||
### Betroffene Controller
|
||||
|
||||
| Service | Controller | Route |
|
||||
| ------- | --------------- | ---------- |
|
||||
| Auth | MeController | `/me/*` |
|
||||
| Storage | AdminController | `/admin/*` |
|
||||
|
||||
---
|
||||
|
||||
## Docker Fixes
|
||||
|
||||
Mehrere Docker-Build Korrekturen.
|
||||
|
||||
### mana-search Symlink Fix
|
||||
|
||||
```dockerfile
|
||||
# VORHER: Symlinks funktionieren nicht im Docker Context
|
||||
COPY packages/shared-types ./packages/shared-types
|
||||
|
||||
# NACHHER: pnpm deploy für korrekte Dependencies
|
||||
RUN pnpm --filter @mana-search/service deploy --prod ./deploy
|
||||
|
||||
FROM node:20-slim
|
||||
COPY --from=build /app/deploy ./
|
||||
```
|
||||
|
||||
### Local Builds auf Mac Mini
|
||||
|
||||
Weitere Services auf lokale Builds umgestellt:
|
||||
|
||||
```yaml
|
||||
presi-backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/presi/apps/backend/Dockerfile
|
||||
# Statt: image: ghcr.io/till-js/presi-backend
|
||||
|
||||
skilltree-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/skilltree/apps/web/Dockerfile
|
||||
|
||||
mana-search:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/mana-search/Dockerfile
|
||||
```
|
||||
|
||||
### Shared Packages in ManaCore Web
|
||||
|
||||
```dockerfile
|
||||
# Fehlende Packages hinzugefügt
|
||||
COPY packages/shared-stores ./packages/shared-stores
|
||||
COPY packages/shared-api-client ./packages/shared-api-client
|
||||
COPY packages/shared-vite-config ./packages/shared-vite-config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Calendar Database Fix
|
||||
|
||||
User ID Felder zu Text geändert:
|
||||
|
||||
```typescript
|
||||
// VORHER (UUID)
|
||||
userId: uuid('user_id').references(() => users.id);
|
||||
|
||||
// NACHHER (Text für externe Auth)
|
||||
userId: text('user_id').notNull();
|
||||
```
|
||||
|
||||
**Grund:** mana-core-auth verwendet String-basierte User IDs, nicht UUIDs.
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
| ---------------------- | ------- | --------------------------- |
|
||||
| **GDPR Self-Service** | 4 | User Data Endpoints, Export |
|
||||
| **Matrix Mobile** | 4 | FAB, Room Restore, Props |
|
||||
| **Mac Mini Stability** | 5 | Health Checks, Recovery |
|
||||
| **Monitoring** | 1 | Alerting Stack |
|
||||
| **Admin API** | 3 | Route Prefix Fixes |
|
||||
| **Docker** | 5 | Symlinks, Local Builds |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
1. **GDPR Export UI** - Download-Button im mana.how Dashboard
|
||||
2. **Matrix E2EE** - Ende-zu-Ende Verschlüsselung aktivieren
|
||||
3. **Alertmanager** - Discord Webhook Integration
|
||||
4. **Service Deployments** - Inventory, NutriPhi, WiseKeep
|
||||
|
|
@ -0,0 +1,719 @@
|
|||
---
|
||||
title: 'Gift Codes, Stripe Integration & Zitare Deployment'
|
||||
description: 'Neues Gift Code System mit Credit-Gutscheinen, Stripe Integration für Subscriptions und Credit-Käufe, Zitare Production Deployment mit multilingualer Quote-Datenbank, sowie Calendar Drag-to-Create und Matrix WhatsApp-Style Navigation'
|
||||
date: 2026-02-13
|
||||
author: 'Till Schneider'
|
||||
category: 'feature'
|
||||
tags:
|
||||
[
|
||||
'gift-codes',
|
||||
'stripe',
|
||||
'subscriptions',
|
||||
'credits',
|
||||
'zitare',
|
||||
'calendar',
|
||||
'matrix',
|
||||
'avatar',
|
||||
'onboarding',
|
||||
'docker',
|
||||
'pwa',
|
||||
]
|
||||
featured: true
|
||||
commits: 55
|
||||
readTime: 18
|
||||
stats:
|
||||
filesChanged: 287
|
||||
linesAdded: 20500
|
||||
linesRemoved: 5752
|
||||
contributors:
|
||||
- name: 'Till Schneider'
|
||||
handle: 'Till-JS'
|
||||
commits: 55
|
||||
workingHours:
|
||||
start: '2026-02-13T11:00'
|
||||
end: '2026-02-14T11:00'
|
||||
---
|
||||
|
||||
Ein massiver Tag mit **55 Commits** und mehreren Major Features:
|
||||
|
||||
- **Gift Codes** - Credit-Gutscheine mit Code-Einlösung
|
||||
- **Stripe Integration** - Subscriptions und Credit-Käufe
|
||||
- **Zitare Deployment** - Production-Ready mit multilingualer Quote-DB
|
||||
- **Calendar UX** - Drag-to-Create, Resize Handles, Live Preview
|
||||
- **Matrix Mobile** - WhatsApp-Style Navigation für PWA
|
||||
- **Avatar Upload** - S3/MinIO Integration mit Onboarding
|
||||
- **ManaCore Dashboard** - Profile, Subscriptions, Credits UI
|
||||
|
||||
---
|
||||
|
||||
## Gift Codes & Credit System
|
||||
|
||||
Neues Geschenkkarten-System für Credit-Gutscheine.
|
||||
|
||||
### Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Gift Code System │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Admin erstellt Code User löst Code ein │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ POST /gifts │ │ POST /gifts/ │ │
|
||||
│ │ │ │ redeem │ │
|
||||
│ │ code: MANA-XXX │ │ code: MANA-XXX │ │
|
||||
│ │ credits: 100 │ │ │ │
|
||||
│ │ maxUses: 10 │ └────────┬────────┘ │
|
||||
│ └─────────────────┘ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Credit Balance │ │
|
||||
│ │ += 100 Credits │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
```typescript
|
||||
// Gift Codes verwalten
|
||||
POST /api/v1/gifts // Code erstellen (Admin)
|
||||
GET /api/v1/gifts // Alle Codes auflisten
|
||||
GET /api/v1/gifts/:code // Code-Details abrufen
|
||||
POST /api/v1/gifts/redeem // Code einlösen (User)
|
||||
DELETE /api/v1/gifts/:code // Code löschen
|
||||
|
||||
// Request Body: Code einlösen
|
||||
{
|
||||
"code": "MANA-WELCOME-2026"
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"success": true,
|
||||
"credits": 100,
|
||||
"message": "100 Credits wurden deinem Konto gutgeschrieben!"
|
||||
}
|
||||
```
|
||||
|
||||
### Database Schema
|
||||
|
||||
```typescript
|
||||
// auth.schema.ts
|
||||
export const giftCodes = authSchema.table('gift_codes', {
|
||||
id: text('id').primaryKey(),
|
||||
code: text('code').notNull().unique(),
|
||||
credits: integer('credits').notNull(),
|
||||
maxUses: integer('max_uses').default(1),
|
||||
currentUses: integer('current_uses').default(0),
|
||||
expiresAt: timestamp('expires_at'),
|
||||
createdBy: text('created_by').references(() => users.id),
|
||||
createdAt: timestamp('created_at').defaultNow(),
|
||||
});
|
||||
|
||||
export const giftRedemptions = authSchema.table('gift_redemptions', {
|
||||
id: text('id').primaryKey(),
|
||||
codeId: text('code_id').references(() => giftCodes.id),
|
||||
userId: text('user_id').references(() => users.id),
|
||||
redeemedAt: timestamp('redeemed_at').defaultNow(),
|
||||
});
|
||||
```
|
||||
|
||||
### Matrix Bot Integration
|
||||
|
||||
Gift Codes können auch via Matrix eingelöst werden:
|
||||
|
||||
```
|
||||
User: !redeem MANA-WELCOME-2026
|
||||
Bot: ✅ Code eingelöst! Du hast 100 Credits erhalten.
|
||||
Aktuelles Guthaben: 250 Credits
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stripe Integration
|
||||
|
||||
Vollständige Stripe-Anbindung für Payments.
|
||||
|
||||
### Subscription Plans
|
||||
|
||||
```typescript
|
||||
// Subscription Tiers
|
||||
const SUBSCRIPTION_PLANS = {
|
||||
free: {
|
||||
name: 'Free',
|
||||
price: 0,
|
||||
credits: 50, // monatlich
|
||||
features: ['Basic Apps', '50 Credits/Monat'],
|
||||
},
|
||||
plus: {
|
||||
name: 'Plus',
|
||||
price: 4.99,
|
||||
stripePriceId: 'price_plus_monthly',
|
||||
credits: 500,
|
||||
features: ['Alle Apps', '500 Credits/Monat', 'Priority Support'],
|
||||
},
|
||||
pro: {
|
||||
name: 'Pro',
|
||||
price: 14.99,
|
||||
stripePriceId: 'price_pro_monthly',
|
||||
credits: 2000,
|
||||
features: ['Alle Apps', '2000 Credits/Monat', 'API Zugang', 'Priorität'],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Credit Purchases
|
||||
|
||||
```typescript
|
||||
// Einmal-Käufe
|
||||
POST /api/v1/credits/purchase
|
||||
{
|
||||
"package": "credits_500" // 500 Credits für 4.99€
|
||||
}
|
||||
|
||||
// Verfügbare Pakete
|
||||
const CREDIT_PACKAGES = {
|
||||
credits_100: { credits: 100, price: 0.99, stripePriceId: '...' },
|
||||
credits_500: { credits: 500, price: 4.99, stripePriceId: '...' },
|
||||
credits_1000: { credits: 1000, price: 8.99, stripePriceId: '...' },
|
||||
credits_5000: { credits: 5000, price: 39.99, stripePriceId: '...' },
|
||||
};
|
||||
```
|
||||
|
||||
### Webhook Handler
|
||||
|
||||
```typescript
|
||||
// stripe.controller.ts
|
||||
@Post('webhook')
|
||||
async handleWebhook(@Req() req: Request) {
|
||||
const event = this.stripe.webhooks.constructEvent(
|
||||
req.body,
|
||||
req.headers['stripe-signature'],
|
||||
this.webhookSecret,
|
||||
);
|
||||
|
||||
switch (event.type) {
|
||||
case 'checkout.session.completed':
|
||||
await this.handleCheckoutComplete(event.data.object);
|
||||
break;
|
||||
case 'customer.subscription.updated':
|
||||
await this.handleSubscriptionUpdate(event.data.object);
|
||||
break;
|
||||
case 'invoice.paid':
|
||||
await this.handleInvoicePaid(event.data.object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zitare Production Deployment
|
||||
|
||||
Zitare ist jetzt live auf zitare.mana.how!
|
||||
|
||||
### Docker Infrastructure
|
||||
|
||||
```yaml
|
||||
# docker-compose.macmini.yml
|
||||
zitare-backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/zitare/apps/backend/Dockerfile
|
||||
ports:
|
||||
- '3007:3007'
|
||||
environment:
|
||||
DATABASE_URL: ${ZITARE_DATABASE_URL}
|
||||
MANA_CORE_AUTH_URL: http://mana-core-auth:3001
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3007/health']
|
||||
|
||||
zitare-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/zitare/apps/web/Dockerfile
|
||||
ports:
|
||||
- '5018:5018'
|
||||
depends_on:
|
||||
- zitare-backend
|
||||
```
|
||||
|
||||
### @zitare/content Package
|
||||
|
||||
Neues Package für shared Quotes:
|
||||
|
||||
```typescript
|
||||
// packages/zitare-content/src/quotes.ts
|
||||
export interface Quote {
|
||||
id: string;
|
||||
text: string;
|
||||
author: string;
|
||||
source?: string;
|
||||
year?: number;
|
||||
language: 'de' | 'en';
|
||||
category: QuoteCategory;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export const quotes: Quote[] = [
|
||||
{
|
||||
id: 'goethe-001',
|
||||
text: 'Es ist nicht genug zu wissen, man muss auch anwenden...',
|
||||
author: 'Johann Wolfgang von Goethe',
|
||||
source: 'Wilhelm Meisters Wanderjahre',
|
||||
year: 1829,
|
||||
language: 'de',
|
||||
category: 'wisdom',
|
||||
tags: ['wissen', 'handeln', 'motivation'],
|
||||
},
|
||||
// ... 500+ Zitate
|
||||
];
|
||||
```
|
||||
|
||||
### Multilingual Support
|
||||
|
||||
```typescript
|
||||
// Backend: Sprache aus Header
|
||||
@Get('quote/daily')
|
||||
getDailyQuote(@Headers('accept-language') lang: string) {
|
||||
const language = lang?.startsWith('de') ? 'de' : 'en';
|
||||
return this.quoteService.getDailyQuote(language);
|
||||
}
|
||||
|
||||
// Frontend: i18n Integration
|
||||
const { t, locale } = useTranslation();
|
||||
const quote = await fetchDailyQuote($locale);
|
||||
```
|
||||
|
||||
### Quote Metadata
|
||||
|
||||
Alle Zitate mit vollständigen Metadaten:
|
||||
|
||||
| Feld | Beschreibung |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `author` | Vollständiger Name |
|
||||
| `source` | Buch/Werk/Rede |
|
||||
| `year` | Entstehungsjahr |
|
||||
| `category` | wisdom, motivation, love, life, ... |
|
||||
| `tags` | Suchbare Schlagwörter |
|
||||
|
||||
---
|
||||
|
||||
## Calendar UX Improvements
|
||||
|
||||
Mehrere UX-Verbesserungen für den Kalender.
|
||||
|
||||
### Drag-to-Create
|
||||
|
||||
Events können jetzt durch Drag & Drop erstellt werden:
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let dragStart = $state<{ hour: number; day: Date } | null>(null);
|
||||
let dragEnd = $state<{ hour: number; day: Date } | null>(null);
|
||||
|
||||
function handleMouseDown(hour: number, day: Date) {
|
||||
dragStart = { hour, day };
|
||||
}
|
||||
|
||||
function handleMouseMove(hour: number, day: Date) {
|
||||
if (dragStart) {
|
||||
dragEnd = { hour, day };
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseUp() {
|
||||
if (dragStart && dragEnd) {
|
||||
createEvent({
|
||||
start: new Date(dragStart.day.setHours(dragStart.hour)),
|
||||
end: new Date(dragEnd.day.setHours(dragEnd.hour)),
|
||||
});
|
||||
}
|
||||
dragStart = null;
|
||||
dragEnd = null;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Live Time Preview
|
||||
|
||||
Beim Ziehen wird die neue Zeit live angezeigt:
|
||||
|
||||
```svelte
|
||||
{#if dragStart && dragEnd}
|
||||
<div class="preview-overlay">
|
||||
{formatTime(dragStart.hour)} - {formatTime(dragEnd.hour)}
|
||||
</div>
|
||||
{/if}
|
||||
```
|
||||
|
||||
### Resize Handles
|
||||
|
||||
Größere Resize-Handles für bessere Touch-Bedienbarkeit:
|
||||
|
||||
```css
|
||||
.event-resize-handle {
|
||||
height: 12px; /* Vorher: 6px */
|
||||
cursor: ns-resize;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
/* Mobile: Noch größer */
|
||||
@media (max-width: 768px) {
|
||||
.event-resize-handle {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mobile UX
|
||||
|
||||
- Tasks standardmäßig ausgeblendet
|
||||
- Auto-Scroll zur aktuellen Stunde
|
||||
- Mikrofon-Button in der Input Bar integriert
|
||||
- Padding für Bottom-UI Sichtbarkeit
|
||||
|
||||
---
|
||||
|
||||
## Matrix WhatsApp-Style Navigation
|
||||
|
||||
Neue Mobile-Navigation für die Matrix PWA.
|
||||
|
||||
### Navigation Pattern
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ← Rooms Matrix Settings│ ← Header
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ General > │ │
|
||||
│ ├─────────────────────────────────┤ │
|
||||
│ │ Development > │ │
|
||||
│ ├─────────────────────────────────┤ │
|
||||
│ │ @user:mana.how > │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ │
|
||||
│ │ ← Room List
|
||||
│ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ + New Chat │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
↓ Tap on Room ↓
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ← General ⋮ │ ← Room Header
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Alice: Hey, wie geht's? │
|
||||
│ │
|
||||
│ Bob: Gut, danke! │
|
||||
│ │
|
||||
│ Alice: Hast du das Feature gesehen? │
|
||||
│ │
|
||||
├─────────────────────────────────────────┤
|
||||
│ [Message input...] Send │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### iOS PWA Swipe-Back
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let touchStartX = 0;
|
||||
|
||||
function handleTouchStart(e: TouchEvent) {
|
||||
touchStartX = e.touches[0].clientX;
|
||||
}
|
||||
|
||||
function handleTouchEnd(e: TouchEvent) {
|
||||
const touchEndX = e.changedTouches[0].clientX;
|
||||
const diff = touchEndX - touchStartX;
|
||||
|
||||
// Swipe from left edge
|
||||
if (touchStartX < 30 && diff > 100) {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div ontouchstart={handleTouchStart} ontouchend={handleTouchEnd}>
|
||||
<!-- Room Content -->
|
||||
</div>
|
||||
```
|
||||
|
||||
### Mobile Web App Meta Tags
|
||||
|
||||
```html
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Avatar Upload & Onboarding
|
||||
|
||||
Neues Avatar-System mit S3/MinIO.
|
||||
|
||||
### Upload Flow
|
||||
|
||||
```typescript
|
||||
// POST /api/v1/me/avatar
|
||||
@Post('avatar')
|
||||
@UseInterceptors(FileInterceptor('avatar'))
|
||||
async uploadAvatar(
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@CurrentUser() user: CurrentUserData,
|
||||
) {
|
||||
// Resize und optimieren
|
||||
const optimized = await sharp(file.buffer)
|
||||
.resize(256, 256)
|
||||
.webp({ quality: 80 })
|
||||
.toBuffer();
|
||||
|
||||
// Upload zu S3/MinIO
|
||||
const key = `avatars/${user.userId}.webp`;
|
||||
await this.storageService.upload(key, optimized, {
|
||||
contentType: 'image/webp',
|
||||
public: true,
|
||||
});
|
||||
|
||||
// URL speichern
|
||||
await this.userService.updateAvatar(user.userId, key);
|
||||
|
||||
return { avatarUrl: this.storageService.getPublicUrl(key) };
|
||||
}
|
||||
```
|
||||
|
||||
### Onboarding Wizard
|
||||
|
||||
Neuer Wizard für neue Nutzer:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Welcome to ManaCore! │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────────────┐ │
|
||||
│ │ 📷 │ ← Avatar │
|
||||
│ │ Upload Photo │ │
|
||||
│ └───────────────┘ │
|
||||
│ │
|
||||
│ Display Name: [________________] │
|
||||
│ │
|
||||
│ Language: [Deutsch ▼] │
|
||||
│ │
|
||||
│ Theme: ○ Light ● Dark ○ Auto │
|
||||
│ │
|
||||
│ [ Continue → ] │
|
||||
│ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ManaCore Dashboard
|
||||
|
||||
Neues Dashboard auf mana.how.
|
||||
|
||||
### Profile Section
|
||||
|
||||
```svelte
|
||||
<div class="profile-card">
|
||||
<Avatar src={user.avatarUrl} size="lg" />
|
||||
<h2>{user.name}</h2>
|
||||
<p>{user.email}</p>
|
||||
|
||||
<div class="stats">
|
||||
<Stat label="Credits" value={credits} />
|
||||
<Stat label="Subscription" value={subscription.name} />
|
||||
<Stat label="Member since" value={formatDate(user.createdAt)} />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Subscription Management
|
||||
|
||||
```svelte
|
||||
<SubscriptionCard
|
||||
current={subscription}
|
||||
plans={SUBSCRIPTION_PLANS}
|
||||
onUpgrade={handleUpgrade}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
```
|
||||
|
||||
### Credits Frontend
|
||||
|
||||
```svelte
|
||||
<CreditsDashboard>
|
||||
<CreditBalance balance={credits} />
|
||||
<CreditHistory {transactions} />
|
||||
<CreditPurchase packages={CREDIT_PACKAGES} />
|
||||
</CreditsDashboard>
|
||||
```
|
||||
|
||||
### Storage Usage Widget
|
||||
|
||||
```svelte
|
||||
<StorageUsageWidget
|
||||
used={storageUsed}
|
||||
total={storageTotal}
|
||||
breakdown={[
|
||||
{ label: 'Photos', size: photosSize, color: 'blue' },
|
||||
{ label: 'Documents', size: docsSize, color: 'green' },
|
||||
{ label: 'Other', size: otherSize, color: 'gray' },
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### API Keys Management
|
||||
|
||||
```svelte
|
||||
<ApiKeysManager
|
||||
keys={apiKeys}
|
||||
onCreate={createKey}
|
||||
onRevoke={revokeKey}
|
||||
services={['stt', 'tts', 'llm']}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings Stores
|
||||
|
||||
Neue Settings-Stores für Zitare und Todo.
|
||||
|
||||
### Pattern
|
||||
|
||||
```typescript
|
||||
// stores/settings.store.ts
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
|
||||
export interface AppSettings {
|
||||
theme: 'light' | 'dark' | 'auto';
|
||||
language: string;
|
||||
notifications: boolean;
|
||||
// App-spezifische Settings
|
||||
}
|
||||
|
||||
export const settings = persisted<AppSettings>('app-settings', {
|
||||
theme: 'auto',
|
||||
language: 'de',
|
||||
notifications: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Zitare Settings
|
||||
|
||||
```typescript
|
||||
interface ZitareSettings extends AppSettings {
|
||||
dailyNotificationTime: string; // "09:00"
|
||||
favoriteCategories: string[];
|
||||
showAuthorInfo: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Todo Settings
|
||||
|
||||
```typescript
|
||||
interface TodoSettings extends AppSettings {
|
||||
defaultPriority: Priority;
|
||||
showCompletedTasks: boolean;
|
||||
sortOrder: 'priority' | 'dueDate' | 'created';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Devlog Activity Grid
|
||||
|
||||
Neue Activity-Seite für Devlogs.
|
||||
|
||||
### Features
|
||||
|
||||
- GitHub-Style Contribution Grid
|
||||
- Commit-Statistiken pro Tag
|
||||
- Hover-Details mit Commit-Count
|
||||
- Link zu Devlog-Einträgen
|
||||
|
||||
### Implementation
|
||||
|
||||
```svelte
|
||||
<ActivityGrid
|
||||
data={devlogStats}
|
||||
colorScale={['#eee', '#9be9a8', '#40c463', '#30a14e', '#216e39']}
|
||||
cellSize={12}
|
||||
gap={3}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Weitere Änderungen
|
||||
|
||||
### Shared UI Refactoring
|
||||
|
||||
LoginPage und InputBar Komponenten verbessert:
|
||||
|
||||
```typescript
|
||||
// Bessere Props-Struktur
|
||||
interface LoginPageProps {
|
||||
locale: string;
|
||||
onLogin: (credentials: LoginCredentials) => Promise<void>;
|
||||
onRegister?: () => void;
|
||||
onForgotPassword?: () => void;
|
||||
showRememberMe?: boolean;
|
||||
allowSocialLogin?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Matrix Bot Enhancements
|
||||
|
||||
- Stats Bot: Detailliertere Statistiken
|
||||
- Todo Bot: Cleaner Response Messages
|
||||
- Offline Mode entfernt - Login erforderlich
|
||||
|
||||
### Docker Local Builds
|
||||
|
||||
Weitere Services auf lokale Builds umgestellt:
|
||||
|
||||
| Service | Grund |
|
||||
| ------------------- | -------------------- |
|
||||
| matrix-todo-bot | Native Dependencies |
|
||||
| matrix-calendar-bot | Native Dependencies |
|
||||
| presi-backend | Schnellere Iteration |
|
||||
| mana-web | Shared Packages |
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
| Bereich | Commits | Highlights |
|
||||
| --------------------- | ------- | ------------------------------- |
|
||||
| **Gift Codes** | 4 | Code-System, Matrix Integration |
|
||||
| **Stripe** | 3 | Subscriptions, Credit Purchases |
|
||||
| **Zitare** | 10 | Docker, Quotes, i18n |
|
||||
| **Calendar** | 6 | Drag-Create, Resize, Mobile UX |
|
||||
| **Matrix** | 8 | WhatsApp-Nav, PWA, Swipe |
|
||||
| **ManaCore** | 8 | Profile, Credits, Storage |
|
||||
| **Avatar/Onboarding** | 3 | S3 Upload, Wizard |
|
||||
| **Settings** | 2 | Zitare, Todo Stores |
|
||||
| **Auth/Docker** | 8 | Healthchecks, Builds |
|
||||
| **Devlog** | 3 | Activity Grid |
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
1. **Stripe Webhooks** - Production Webhook Secret konfigurieren
|
||||
2. **Gift Code UI** - Admin-Panel für Code-Erstellung
|
||||
3. **Subscription Emails** - Bestätigungen und Erinnerungen
|
||||
4. **Matrix E2EE** - Verschlüsselung für DMs aktivieren
|
||||
5. **Zitare Push** - Daily Quote Notifications
|
||||
Loading…
Add table
Add a link
Reference in a new issue