chore: initial Nimuli design spec

main
boban 2026-05-15 22:08:27 +02:00
commit 364d1448c7
1 changed files with 789 additions and 0 deletions

View File

@ -0,0 +1,789 @@
# Nimuli — Master Design Spec
**Datum:** 2026-05-15
**Status:** Design abgeschlossen, bereit für Implementation-Planung
**Autor:** Boban Blaskovic
**Projekt-Typ:** Standalone SaaS (eigene Codebase, getrennt von CluPilot)
---
## 1. Produkt-Übersicht
**Nimuli** ist eine All-in-One Link-Management-Plattform.
**Kern-Features:**
- Short-Links mit Whitelabel-Custom-Domains
- Dynamische QR-Codes (Logo, Farben, alle Types: URL/vCard/WiFi/PDF/MP3/Text)
- Link-in-Bio Microsites mit Block-Editor
- Smart-Routing (Geo, Device, OS, Time, Language, UTM)
- A/B-Testing mit Conversion-Tracking
- Pixel-Injection (Meta/Google/TikTok via Interstitial)
- Klick-Analytics mit Live-Dashboards
- AI-Suite (Auto-Slug, A/B-Generator, Anomalie-Detection, Insight-Reports, Routing-per-Prompt)
- REST-API + Outgoing-Webhooks
- Multi-Workspace (Agency-Use-Case)
**Markt:** DACH-Start, DE+EN i18n, geplant EU-Expansion.
**Marken + Domains:**
- Marke: **Nimuli**
- Marketing-Site: `nimuli.com`
- App/Dashboard: `app.nimuli.com`
- Short-Redirect-Domain: `nimu.li`
- Whitelabel-Subdomains: `*.nimu.li` (User-Subdomain)
- Custom-Domains: `{kunden-domain}.com` via CNAME → nimu.li
**Differenzierung:**
- Pixel-Injection im Standard-Plan (Bitly ignoriert das)
- AI-Suite ab Tag 1 (Konkurrenz hat das nicht oder spärlich)
- Agency-fokussierte Features (Multi-Workspace, Whitelabel, Reseller)
- EU-Hosting, DSGVO-first
---
## 2. Gesamt-Architektur
### Lokale Entwicklung
Single-Host Docker Compose. Alle Services in einem Compose-File:
- `nginx`, `app` (Octane), `queue`, `scheduler`, `reverb`, `mysql`, `redis`, `mailpit`
### Production-Cluster (Hetzner Cloud)
```
┌──────────────────────┐
│ Hetzner LB (Public) │
│ Failover-IP, SSL │
└──────────┬───────────┘
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ web-01 │ │ web-02 │ │ web-03 │
│ Docker │ │ Docker │ │ Docker │
│ app+ │ │ app+ │ │ app+ │
│ nginx │ │ nginx │ │ nginx │
└───┬────┘ └───┬────┘ └───┬────┘
│ │ │
└──────────┬──────────┴──────────┬──────────┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ DB-01 │ │ Redis-01 │
│ MySQL │ │ Cache+ │
│ │ │ Queue+ │
│ │ │ Sessions │
└───────────┘ └───────────┘
┌──────────────┐ ┌──────────────────┐
│ worker-01 │ │ CDN (bunny.net │
│ Queue + │ │ oder Hetzner │
│ Scheduler + │ │ Storage Box CDN) │
│ Reverb │ │ │
└──────────────┘ └──────────────────┘
```
**DNS-Routing:**
- `app.nimuli.com` → LB-IP → web-Pool
- `nimu.li`, `*.nimu.li` → LB-IP → web-Pool (Redirect-Service)
- `ws.nimuli.com` → direkt worker-01 (Reverb, kein LB)
- `{custom-domain}` → CNAME → nimu.li → LB
**Kosten:** ~60-85€/Monat Hetzner.
### Drei Haupt-Subsysteme
1. **Redirect-Service** — Hot-Path. `nimu.li/{slug}` → Redis-Lookup → 302 + async Klick-Event. Ziel <10ms p95.
2. **App-Dashboard**`app.nimuli.com`. Livewire-CRUD, Analytics, Workspace-Mgmt, Billing.
3. **Background-Worker** — Queue für Klick-Events → Rollups, Email, Stripe-Webhooks, AI-Calls, Pixel-Server-Side-Sends, Domain-SSL-Provisioning.
---
## 3. Stack
| Bereich | Technologie |
|---------|-------------|
| Backend | Laravel 12 + Octane (Swoole oder FrankenPHP) |
| Frontend | Livewire v3 + Alpine.js + Tailwind v4 (`@theme`) |
| Realtime | Laravel Reverb (WebSockets) |
| Auth | Laravel Breeze (Livewire-Stack) + `pragmarx/google2fa` (2FA) |
| Permissions | `spatie/laravel-permission` (per Workspace scoped) |
| Modals | Wire Elements Modal |
| Charts | Chart.js 4.x |
| QR-Codes | `chillerlan/php-qrcode` (Backend), QR-JS-Lib für Live-Preview |
| Image-Processing | `intervention/image-laravel` (Resize, WebP, EXIF-Strip, Auto-Rotate) |
| Media-Storage | `spatie/laravel-medialibrary` |
| Audit | `spatie/laravel-activitylog` |
| Settings | `spatie/laravel-settings` |
| Backup | `spatie/laravel-backup` (4×/Tag, 14d Retention) |
| Translations | DB-Table + Redis-Cache (Custom-Implementation, Admin-UI) + Spatie-Translatable für Model-Felder |
| Subscriptions | Laravel Cashier (Stripe) |
| Queue | Redis + Horizon |
| Search | Eloquent `WHERE LIKE` (MySQL FULLTEXT optional) |
| Testing | Pest 3 + Livewire-Testing + Browser-Tests |
| Static Analysis | PHPStan Level 6+ |
| Code-Style | Laravel Pint |
| CI/CD | GitHub Actions → SSH-Deploy zu Hetzner |
| Container | Docker Compose |
| Filesystem | Lokal NAS-Mount (dev), Hetzner Storage Box (prod) |
---
## 4. Modul-Struktur (Domain-Driven)
```
app/
├─ Domains/
│ ├─ Workspace/ Tenant-Container, Members, Invitations
│ ├─ Domain/ Custom Domains, SSL-Provisioning
│ ├─ Link/ Short-Links, Variants (A/B)
│ ├─ QrCode/ QR-Generator, Templates, Renderers
│ ├─ Bio/ Link-in-Bio Microsites
│ ├─ Analytics/ Click-Tracking, Rollups, Queries
│ ├─ Routing/ Smart-Routing Rule-Engine, A/B, Pixel-Inject
│ ├─ Subscription/ Stripe-Billing, Plan-Limits
│ ├─ Translation/ i18n Admin
│ ├─ Ai/ Phase-1 AI-Suite
│ ├─ Webhook/ Outgoing-Webhooks
│ ├─ Api/ REST-API-Layer
│ └─ Ads/ Phase-3 (Meta + Google + TikTok)
├─ Http/
│ ├─ Controllers/ RedirectController (Hot-Path, KEIN Livewire)
│ ├─ Middleware/ ResolveWorkspace, EnforceLimits, LocaleFromUser
│ └─ Resources/ API-Transformers
├─ Livewire/
│ ├─ Layouts/ AppLayout, GuestLayout, PublicBioLayout
│ └─ Pages/ Per Domain
└─ Console/
└─ Commands/ SyncStripe, ProvisionSsl, ResolveDomains, Rollups
```
**Architektur-Prinzipien:**
1. **Domain-Folder-Struktur** statt Type-Folders.
2. **Actions-Pattern** für alle Writes (`CreateLink`, `UpdateLink`, etc.).
3. **Alle Views via Livewire-Components.** Normale Controller nicht für Views nutzen. Ausnahme: `RedirectController` (kein View, nur 302-Response, Hot-Path).
---
## 5. Datenmodell
### Kern-Tabellen
```sql
-- USERS
users
├─ id, ulid, name, email (unique), password
├─ email_verified_at, two_factor_secret, two_factor_recovery
├─ locale (de/en), theme (dark/light/system)
├─ default_workspace_id (nullable, fk)
└─ timestamps, soft_deletes
-- WORKSPACES (Tenants)
workspaces
├─ id, ulid (unique, in URL), name, slug (display)
├─ owner_id (fk users)
├─ plan_id (fk plans), trial_ends_at
├─ branding (json: logo, primary_color, accent_color)
├─ stripe_id, stripe_customer_metadata (json)
└─ timestamps, soft_deletes
workspace_members
├─ id, workspace_id (fk), user_id (fk)
├─ role (enum: owner, admin, editor, viewer)
├─ invited_by, joined_at
└─ unique(workspace_id, user_id)
workspace_invitations
├─ id, workspace_id, email, role, token (unique)
├─ invited_by, expires_at, accepted_at
└─ timestamps
billing_profiles
├─ id, workspace_id (fk), billing_user_id (fk users)
├─ company_name (nullable), vat_number (nullable, VIES-validated)
├─ address_line_1, address_line_2, city, postal_code, country
├─ email_billing
└─ timestamps
-- DOMAINS
domains
├─ id, workspace_id (fk), hostname (unique)
├─ type (enum: short, bio, both)
├─ verified_at, verification_token
├─ ssl_status (pending/active/failed), ssl_issued_at, ssl_expires_at
├─ is_default (bool, true für system nimu.li)
└─ timestamps
-- LINKS
links
├─ id, ulid, workspace_id (fk), domain_id (fk, nullable → nimu.li default)
├─ slug (varchar 64, indexed mit domain_id unique)
├─ target_url (text)
├─ title, description, favicon_url
├─ status (active/disabled/expired)
├─ password (nullable, encrypted)
├─ expires_at, click_limit
├─ rules (json: smart-routing-config)
├─ pixel_config (json: meta_id, google_id, tiktok_id)
├─ tags (json array)
├─ created_by, updated_by
└─ timestamps, soft_deletes
link_variants -- A/B-Testing
├─ id, link_id (fk), label, target_url, weight
├─ clicks_count, conversions_count
└─ timestamps
-- QR CODES
qr_codes
├─ id, ulid, workspace_id, link_id (nullable für direct-QR)
├─ type (url/vcard/wifi/pdf/mp3/text)
├─ payload (json), style (json: colors, logo, frame)
├─ is_dynamic, scan_limit, expires_at
└─ timestamps, soft_deletes
-- LINK-IN-BIO
bio_pages
├─ id, ulid, workspace_id, domain_id (nullable)
├─ slug (unique mit domain_id), title, description, og_image
├─ theme (json), is_published
└─ timestamps, soft_deletes
bio_blocks
├─ id, bio_page_id (fk), position, type
├─ config (json: type-specific)
└─ timestamps
-- ANALYTICS (Raw, 90 Tage retention, monatlich partitioniert)
clicks
├─ id (bigint), link_id (fk), variant_id (nullable)
├─ workspace_id (denormalized)
├─ clicked_at (indexed)
├─ ip_hash (sha256 mit daily-rotating salt — DSGVO)
├─ country, city, device, os, browser
├─ referrer_host, referrer_path
├─ utm_source, utm_medium, utm_campaign, utm_content, utm_term, language
└─ append-only, no updates
-- ANALYTICS Rollups
clicks_hourly (link_id, hour, country, device, count, unique_visitors)
clicks_daily (link_id, day, country, count, unique_visitors)
clicks_by_referrer (link_id, day, referrer_host, count)
clicks_by_utm (link_id, day, utm_source, utm_medium, utm_campaign, count)
-- BILLING
plans
├─ id, name, stripe_price_id, monthly_price_cents
├─ workspace_limit, member_limit, custom_domain_limit
├─ link_limit, click_limit_monthly
└─ features (json)
subscriptions, subscription_items (Cashier-Standard)
-- TRANSLATIONS
translations
├─ id, locale (de/en), key, value (text)
├─ group, namespace, updated_by
└─ unique(locale, namespace, group, key), INDEX(locale, group)
-- API + WEBHOOKS
api_tokens (Sanctum-Standard)
webhooks
├─ id, workspace_id, url, secret, events (json), is_active
└─ timestamps
webhook_deliveries
├─ id, webhook_id, event, payload, status, response_code, attempts
└─ timestamps
-- AUDIT
activity_log (Spatie-Standard)
```
### Redis-Keys
```
link:{slug} Hash (default nimu.li-Domain)
link:{domain_id}:{slug} Hash (Custom-Domain)
→ target, status, rules_json, expires_at, workspace_id, pixel_config
clicks:{link_id}:{date} Counter (INCR pro Klick)
clicks:{workspace_id}:monthly:{ym} Counter für Plan-Enforcement
lang:{locale} Hash mit allen Translation-Keys
workspace:{ulid} Hash mit cached Settings
ratelimit:redirect:{ip} Sliding-Window-Counter
session:{id} Laravel-Session
ab_sticky:{link_id}:{ip_hash} ULID des Variants, TTL 24h
```
### Wichtige Constraints
- `(domain_id, slug)` unique — gleicher Slug auf verschiedenen Domains erlaubt
- `clicks` append-only
- Soft-Deletes auf User-Content
- Stripe-Customer auf `workspaces`, nie auf `users`
- IP-Adresse niemals roh — immer SHA256(ip + daily_salt) mit Salt-Rotation nach 24h
---
## 6. Redirect-Flow + Smart-Routing
### Request-Pipeline
```
1. GET https://nimu.li/x7k
2. nginx → app (Octane)
3. RedirectController::__invoke('x7k')
4. Domain-Resolve via Cache (Host-Header)
5. Redis HGETALL link:{domain_id}:{slug} (Fallback DB)
6. Pre-Checks: status, expires_at, click_limit, password, plan_click_limit
7. Bot-Detection (UA-Pattern, optional AbuseIPDB)
8. Smart-Routing-Resolve (Rules + Context: geo/device/os/time/lang/referrer/utm)
9. A/B-Variant-Pick mit Sticky-Hash (IP+Link, 24h Redis-TTL)
10. Pixel-Injection-Decision (Interstitial-HTML oder direkt 302)
11. Fire-and-forget Click-Event (Redis-INCR + Queue-Job)
12. 302-Response (Ziel <10ms p95)
```
### Smart-Routing Rule-Engine
JSON-Konfiguration pro Link mit Priorisierten Rules:
```json
{
"rules": [
{
"id": "rule-1",
"priority": 1,
"conditions": [
{ "field": "device", "op": "in", "value": ["mobile"] },
{ "field": "os", "op": "equals", "value": "ios" }
],
"match": "all",
"target": "https://apps.apple.com/app/123"
},
{
"id": "rule-2",
"priority": 2,
"conditions": [
{ "field": "country", "op": "in", "value": ["DE","AT","CH"] }
],
"match": "all",
"target": "https://example.de"
}
],
"fallback": "https://example.com"
}
```
**Rule-Fields:** `country`, `city`, `language`, `device`, `os`, `time_hour`, `weekday`, `date`, `referrer_host`, `utm_*`
**Operators:** `equals`, `not_equals`, `in`, `not_in`, `contains`, `starts_with`, `between`, `greater_than`, `less_than`
**Match-Modes:** `all` (AND), `any` (OR)
**Engine** evaluiert in Priority-Reihenfolge, erste Match gewinnt. Fallback wenn keine Match.
### Klick-Verarbeitung
```
RecordClickJob:
1. INSERT INTO clicks (...)
2. Broadcast(new ClickRecordedEvent($click)) → Reverb → Live-Dashboard
3. Wenn Conversion-API enabled (Phase 2): SendMetaCapiJob, SendGoogleConversionJob
4. Webhook-Trigger: workspace.webhooks where event='click.recorded'
```
### Rollup-Schedule
| Cron | Job |
|------|-----|
| every 15 min | RollupHourlyJob (clicks → clicks_hourly) |
| daily 02:00 | RollupDailyJob (clicks_hourly → clicks_daily) |
| daily 03:00 | PruneRawClicksJob (DELETE WHERE clicked_at < now()-90d) |
| daily 04:00 | CheckSslExpiryJob |
| hourly | VerifyPendingDomainsJob |
| weekly mon 09:00 | SendWeeklyInsightReportJob (AI-Feature) |
| 00:00, 06:00, 12:00, 18:00 | DatabaseBackupJob |
---
## 7. Multi-Tenancy
### Workspace-Modell
- Single-DB, `workspace_id` Foreign Key auf allen Workspace-Resourcen
- ULID statt sequenzieller ID in URL für Unguessability
- URL-Pattern: `app.nimuli.com/w/{workspace_ulid}/...`
- Solo-User: Auto-Redirect zu `default_workspace_ulid`
- Multi-Workspace-User (Agency): URL-Slug bestimmt aktiven Kontext, Multi-Tab-fähig
### Workspace-Rollen
| Rolle | Rechte |
|-------|--------|
| Owner | Alles + Billing + Workspace-Delete |
| Admin | Alles außer Billing + Workspace-Delete |
| Editor | Links/QR/Bio erstellen+bearbeiten+löschen |
| Viewer | Nur Lesen, Analytics |
### Plan-Limits (vorläufig)
| Plan | Workspaces/User | Members/Workspace | Custom Domains | Links | Klicks/Monat | Preis |
|------|----------------|-------------------|----------------|-------|--------------|-------|
| Free | 1 | 1 | 0 (nimu.li only) | 50 | 1.000 | 0€ |
| Pro | 1 | 3 | 1 | 1.000 | 25.000 | 19€ |
| Business | 3 | 10 | 5 | 10.000 | 250.000 | 79€ |
| Agency | unlimited | unlimited | unlimited | unlimited | 5M (pay-as-you-go danach) | 199€ |
---
## 8. Billing
### Stripe-Setup
- Stripe + Stripe Tax + PayPal als Method + SEPA-Lastschrift
- Customer pro Workspace (nicht pro User)
- Cashier für Subscription-Management
- Webhook-Endpoint mit Signature-Verify + Idempotency-Check
### Rechnungs-Konzept
- Rechnung zeigt **Billing-Profile-Daten** (User oder Firma) oben
- Custom-Field "Workspace": {workspace.name}
- Footer: "Bezahlt für Workspace: {workspace.name}"
- VAT-ID-Validation via VIES (EU-VAT-MOSS-Konformität)
- Reverse-Charge B2B intra-EU automatisch via Stripe Tax
### Plan-Enforcement
- Limit-Checks vor Action (Pre-Flight)
- Redis-Counter für Klick-Limit (real-time)
- Soft-Block bei 90% Klick-Limit (Warning), Hard-Block bei 100% (Redirect-Page "Plan-Limit erreicht")
- Owner-Email bei Limit-Hit + Upgrade-CTA
---
## 9. i18n + Translation-System
### DB-backed Translations
```
DB-Table: translations
├─ id, locale, namespace, group, key, value, updated_by
└─ unique(locale, namespace, group, key)
Redis:
lang:de → Hash { 'auth.login': 'Anmelden', ... }
lang:en → Hash { 'auth.login': 'Sign in', ... }
```
**Helper:**
```php
__('auth.login')
→ Redis HGET lang:{current_locale} auth.login
→ Fallback: DB → Cache → Lang-File
```
**Admin-UI:** Translation-Manager (CRUD über Keys, Diff-View zwischen Locales, Bulk-Import/Export, Missing-Key-Auto-Add bei `__()`-Call in Dev-Mode).
### Sprachen Phase 1
DE + EN. Phase 2 EU-Expansion: FR, ES, IT, NL.
### Model-Translations
`spatie/laravel-translatable` für Bio-Pages (multi-language Bio-Inhalte für internationale Brands).
---
## 10. Theme + Design-System
### Dual-Mode
- **Dark Mode (Default):** A-Style — CluPilot-inspiriert, Dark-Tech, Neon-Akzente, Mono-Zahlen
- **Light Mode:** B-Style — Vibrant-Modern, Gradients (Lila→Pink), friendly
### Tailwind v4 `@theme`
```css
@import "tailwindcss";
@theme {
--color-bg: #0a0a0f;
--color-s1: #111117;
--color-s2: #16161e;
--color-s3: #1c1c26;
--color-b1: rgb(255 255 255 / 6%);
--color-b2: rgb(255 255 255 / 10%);
--color-t1: #f0f0f5;
--color-t2: #8888a0;
--color-t3: #44445a;
--color-blue: #4f8ef7;
--color-green: #3ecf8e;
--color-amber: #f5a623;
--color-red: #f05252;
--font-mono: 'JetBrains Mono', monospace;
--font-sans: 'Inter', sans-serif;
}
[data-theme="light"] {
--color-bg: #ffffff;
--color-s1: #ffffff;
--color-s2: #f9fafb;
--color-s3: #f3f4f6;
--color-b1: #e5e7eb;
--color-b2: #d1d5db;
--color-t1: #111827;
--color-t2: #6b7280;
--color-t3: #9ca3af;
--color-blue: #7c3aed;
--color-green: #10b981;
--color-amber: #f59e0b;
--color-red: #ef4444;
}
```
**Custom-Style-Regel:** Keine Inline-Styles im Markup außer für Progressbar-Width-Berechnungen.
### Layout-Struktur
```
┌────────┬─────────────────────────────────────┐
│Sidebar │ Topbar (Workspace-Switcher, │
│ 240px │ Search, User-Menu, Theme-Toggle) │
│fixed │─────────────────────────────────────│
│ │ │
│ Logo │ Content Area │
│ WS-Box │ (Livewire-Page-Component) │
│ Nav │ │
│ Plan │ │
│ Box │ │
└────────┴─────────────────────────────────────┘
```
Mobile (<768px): Sidebar als Drawer mit Hamburger.
### Mobile-First
- Default-Styles für Mobile, dann `md:`/`lg:` für Desktop
- Touch-Targets min 44×44px
- Drawer-Nav, Bottom-Action-Bars, Swipe-Gesten
- PWA-Manifest + Service-Worker (Offline-Read für Analytics)
---
## 11. Sicherheit + DSGVO
### Auth-Härtung
- Argon2id Passwort-Hashing
- 2FA via TOTP + Recovery-Codes
- Cookie-Flags: `secure`, `httpOnly`, `SameSite=Lax`
- Login-Rate-Limit 5/15min/IP
- Pwned-Password-Check via HaveIBeenPwned-API
### Workspace-Isolation
- Global Scope `workspace_id = current_workspace`
- Policies + Middleware-Check
- 404 statt 403 bei Cross-Workspace (Existenz nicht leaken)
### Input-Validation
- Mass-Assignment: `$guarded = ['id', 'workspace_id', 'created_by']` global
- Validierung via FormRequests
- HTMLPurifier auf User-Content (Bio-Blocks)
- SVG-Sanitizer auf Logo-Uploads
- File-Upload-Whitelist, Mime-Check, EXIF-Strip
### Webhooks
- HMAC-SHA256-Signatur `X-Nimuli-Signature` auf Outgoing
- SSRF-Schutz: private IP-Ranges blockiert (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16)
- Retry mit Exponential-Backoff (5 Versuche)
### Custom-Domain
- DNS-TXT-Record-Verifizierung
- Let's-Encrypt-ACME-SSL
- Renewal-Cron 30d vor Expiry mit Alerts
### Redirect-Härtung
- Rate-Limit 100 req/min/IP (Sliding-Window-Redis)
- Bot-Detection
- Google-Safe-Browsing-Check auf target_url bei Create + Daily-Re-Check
- Phishing-Domains werden auto-disabled
### DSGVO
- IP-Hash mit täglich-rotierendem Salt (24h irreversibel)
- Cookie-Banner-Logik in Bio-Pages (per Workspace-Setting)
- AVV-PDF auto-generierbar
- Datenexport (Art. 20): JSON-Bundle, Hintergrund-Job, Signed-URL 7d
- Recht auf Vergessen: 30d Soft-Delete → Hard-Delete
- Drittland-Transfers (Stripe, OpenAI/Anthropic, MaxMind): SCC + DPF aktiviert
- EU-Hosting (Hetzner DE)
### Security-Headers
- Strict-CSP, HSTS, X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy strict-origin
---
## 12. Backup + Disaster-Recovery
- `spatie/laravel-backup`
- 4× täglich MySQL-Dump (00:00, 06:00, 12:00, 18:00)
- Retention 14 Tage (= 56 Dumps gespeichert)
- Storage: Hetzner Storage Box (separate Region)
- Verschlüsselt
- Wöchentliche Restore-Drill auf Test-Instanz
---
## 13. Test-Strategie
| Layer | Tool | Ziel |
|-------|------|------|
| Unit | Pest | Actions, Resolvers, Rule-Engine |
| Feature | Pest | HTTP-Routes, Auth, Workspace-Scoping |
| Livewire | Pest + Livewire-Testing | Jede Page-Component, Forms, Actions |
| Integration | Pest | Stripe-Webhook (mocked), Email, Queue-Jobs |
| E2E | Pest 3 Browser | Critical-Paths: Register → Workspace → Link → Klick → Analytics |
| Performance | k6 | Redirect p95 <10ms, Dashboard <500ms |
| Security | OWASP-ZAP + manuell | Vor Launch |
**TDD-Approach:** Red-Green-Refactor, Test zuerst. 80%+ Coverage als Smell-Indikator.
**CI-Pipeline (GitHub Actions):**
```yaml
on: [push, pull_request]
jobs:
test:
- composer install
- npm ci + npm run build
- php artisan migrate (test DB)
- vendor/bin/pest --parallel
- php artisan pint --test
- php artisan phpstan analyse
deploy (on main):
- SSH zu Hetzner
- Pull, migrate, restart-Container
```
**Health-Checks:**
- `/health` endpoint: DB, Redis, Disk, Queue-Length
- UptimeRobot/BetterStack 1min-Polling
- Status-Page öffentlich (status.nimuli.com)
---
## 14. Phasen-Roadmap
**Hinweis:** Big-Bang-Launch Phase 1 (Wunsch des Users, kein Wave-Split). Total 12-16 Wochen Vollzeit.
### Phase 1 — Core + AI + API/Webhooks (12-16 Wochen)
**Inhalt:**
- Setup, Auth+2FA, Workspaces, Domains, Links, Analytics, Billing, Whitelabel, Theme-Switch, i18n
- Smart-Routing, A/B-Testing, Link-Schutz, Pixel-Injection, QR-Codes, Link-in-Bio
- AI-Suite (Auto-Slug, A/B-Generator, A/B-Explainer, Routing-per-Prompt, Anomalie-Detection, Insight-Report)
- API v1 (REST + Sanctum + Rate-Limit per Plan) + Outgoing-Webhooks
**Launch-Kriterien:**
- Stripe Live-Mode, 4 Pläne kaufbar
- DSGVO-Audit clean (AVV, Datenschutz, Cookie-Logik)
- Sentry, Uptime-Monitor, Status-Page
- Onboarding-Tour, Email-Sequenzen, Hilfe-Docs
### Phase 2 — 3rd-Party Integrationen (6-8 Wochen)
- Zapier-App-Listing
- Make.com-Modules
- Slack OAuth + Notifications
- Discord Webhooks
- Shopify-App-Listing
- Conversion-API: Meta CAPI + Google Enhanced Conversions Server-Side
### Phase 2.5 (parallel) — Mobile App iOS+Android
- React Native + Expo
- Features: Login, Workspace-Switch, Quick-Link-Create (Share-Extension), Analytics-Read-Only, Push-Notifications, QR-Scanner
- Push-Service: Expo Push oder Firebase FCM
### Phase 3 — Ads-Management (10-14 Wochen)
- Meta OAuth + Ad-Read + Ad-Create + CAPI-Full
- Google Ads OAuth + Read + Create + Enhanced Conversions
- TikTok Ads (optional)
- Unified Campaign-View Cross-Platform
- Budget-Alerts + Auto-Pause-Rules (CPL-basiert)
### Post-Phase-3 (Optional/On-Demand)
- ClickHouse-Migration bei >50M Klicks/Monat
- Edge-Redirects via Cloudflare Workers (globale Latenz <50ms)
- Marketplace für Bio-Templates (Revenue-Share)
- White-Label-Reseller-Tier
- Custom-Reports-Builder (Drag-Drop)
---
## 15. Risiken + Mitigations
| Risiko | Likelihood | Impact | Mitigation |
|--------|-----------|--------|-----------|
| Spam/Phishing-Missbrauch | hoch | hoch | Safe-Browsing-Check, Manual-Review-Queue für Free-Tier, Account-Auto-Suspend |
| DDoS auf Redirects | mittel | hoch | Rate-Limit Redis, LB-DDoS-Schutz, später Cloudflare-Edge |
| MySQL überlastet bei Klick-Spike | mittel | mittel | INSERT async via Queue, Rollups, Plan-Click-Limits |
| Custom-Domain SSL läuft ab | hoch | mittel | Cron-Renewal 30/14/7/1d-Alerts |
| OpenAI/Anthropic-API-Ausfall | mittel | niedrig | Graceful Degradation, Core läuft weiter |
| Stripe-Webhook-Verlust | niedrig | mittel | Idempotency-Store + Daily-Reconciliation-Job |
| Hetzner-Ausfall / DB-Korruption | niedrig | hoch | 4×/Tag Backup, 14d Retention, separate Region, Wochen-Restore-Drill |
| Konkurrenz launcht ähnliches AI-Feature | hoch | mittel | Iterations-Tempo, Agency-Nische-Fokus |
| Solo-Dev-Burnout (Big-Bang 16 Wochen) | hoch | hoch | Vom User akzeptiert; trotzdem regelmäßige Checkpoints, MVP-Mindset |
| GDPR-Klage Pixel-Injection | mittel | hoch | Consent-Banner Pflicht-Feature, AVV, transparente Datenschutzerklärung |
---
## 16. Offene Punkte für Implementation-Plan
Diese Details werden im Implementation-Plan konkretisiert:
- Exakte Migrationen-Reihenfolge
- Konkrete Route-Definitionen (Web + API)
- Konkrete Livewire-Component-Liste mit View-Pfaden
- AI-Provider-Wahl (Anthropic Claude vs. OpenAI vs. beide via Adapter-Pattern)
- Reverb-Channel-Definitionen + Authorization-Logic
- Stripe-Product/Price-Setup-Skript
- Seed-Daten für Demo-Workspace
- E-Mail-Template-Liste
- Onboarding-Tour-Steps
- Marketing-Site (`nimuli.com`) — Stack: Statisches Astro/Next oder Teil der App?
---
## 17. Erfolgs-Kriterien Spec
- [x] Architektur klar (Dev + Prod)
- [x] Stack festgelegt
- [x] Modul-Struktur dokumentiert
- [x] Datenmodell komplett
- [x] Redirect-Pipeline beschrieben
- [x] Multi-Tenancy-Modell klar
- [x] Billing-Konzept (inkl. Rechnungs-Anzeige)
- [x] i18n-System mit DB+Redis
- [x] Theme + Tailwind-Setup
- [x] Sicherheit + DSGVO
- [x] Backup-Strategie
- [x] Test-Strategie
- [x] Phasen-Roadmap
- [x] Risiken + Mitigations
**Spec-Status:** Bereit für `writing-plans`-Skill zur Erstellung des Implementation-Plans.