diff --git a/20250119_Logo_Botkozept.svg b/20250119_Logo_Botkozept.svg new file mode 100644 index 0000000..8318377 --- /dev/null +++ b/20250119_Logo_Botkozept.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BOTKONZEPT_README.md b/BOTKONZEPT_README.md new file mode 100644 index 0000000..35f0f69 --- /dev/null +++ b/BOTKONZEPT_README.md @@ -0,0 +1,434 @@ +# 🤖 BotKonzept - SaaS Platform für KI-Chatbots + +## 📋 Übersicht + +BotKonzept ist eine vollständige SaaS-Plattform für KI-Chatbots mit automatischer Kundenregistrierung, Trial-Management und E-Mail-Automation. + +### Hauptfunktionen + +- ✅ **Automatische Kundenregistrierung** über Website +- ✅ **Automatische LXC-Instanz-Erstellung** für jeden Kunden +- ✅ **7-Tage-Trial** mit automatischen Upgrade-Angeboten +- ✅ **E-Mail-Automation** (Tag 3, 5, 7) +- ✅ **Rabatt-System** (30% → 15% → Normalpreis) +- ✅ **Supabase-Integration** für Kunden-Management +- ✅ **Stripe/PayPal** Payment-Integration +- ✅ **DSGVO-konform** (Daten in Deutschland) + +## 🏗️ Architektur + +``` +┌─────────────────────────────────────────────────────────────┐ +│ BotKonzept Platform │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ +│ │ Website │─────▶│ n8n Webhook │─────▶│ PVE20 │ │ +│ │ botkonzept.de│ │ Registration │ │ install.sh│ │ +│ └──────────────┘ └──────────────┘ └───────────┘ │ +│ │ │ │ │ +│ │ ▼ ▼ │ +│ │ ┌──────────────┐ ┌───────────┐ │ +│ │ │ Supabase │ │ LXC (CTID)│ │ +│ │ │ PostgreSQL │ │ n8n │ │ +│ │ │ Customers │ │ PostgREST│ │ +│ │ │ Instances │ │ Postgres │ │ +│ │ └──────────────┘ └───────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Trial Mgmt │ │ Email Auto │ │ +│ │ Workflow │─────▶│ Day 3,5,7 │ │ +│ │ (Cron Daily) │ │ Postfix/SES │ │ +│ └──────────────┘ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 📁 Projekt-Struktur + +``` +customer-installer/ +├── botkonzept-website/ # Landing Page & Registrierung +│ ├── index.html # Hauptseite +│ ├── css/style.css # Styling +│ └── js/main.js # JavaScript (Form-Handling) +│ +├── sql/ +│ ├── botkonzept_schema.sql # Datenbank-Schema +│ └── init_pgvector.sql # Vector-DB für RAG +│ +├── BotKonzept-Customer-Registration-Workflow.json +│ # n8n Workflow für Registrierung +│ +├── BotKonzept-Trial-Management-Workflow.json +│ # n8n Workflow für Trial-Management +│ +├── install.sh # LXC-Installation +├── libsupabase.sh # Helper-Funktionen +├── setup_nginx_proxy.sh # NGINX Reverse Proxy +└── BOTKONZEPT_README.md # Diese Datei +``` + +## 🚀 Installation & Setup + +### 1. Datenbank einrichten + +```bash +# Supabase PostgreSQL Schema erstellen +psql -U postgres -d customer < sql/botkonzept_schema.sql +``` + +### 2. n8n Workflows importieren + +1. Öffnen Sie n8n: `https://n8n.userman.de` +2. Importieren Sie die Workflows: + - `BotKonzept-Customer-Registration-Workflow.json` + - `BotKonzept-Trial-Management-Workflow.json` +3. Konfigurieren Sie die Credentials: + - **SSH (PVE20):** Private Key für Proxmox + - **PostgreSQL (Supabase):** Lokale Supabase-Instanz + - **SMTP (Postfix/SES):** E-Mail-Versand + +### 3. Website deployen + +```bash +# Website-Dateien auf Webserver kopieren +cd botkonzept-website +rsync -avz . user@botkonzept.de:/var/www/botkonzept/ + +# Oder lokal testen +python3 -m http.server 8000 +# Öffnen: http://localhost:8000 +``` + +### 4. Webhook-URL konfigurieren + +In `botkonzept-website/js/main.js`: + +```javascript +const CONFIG = { + WEBHOOK_URL: 'https://n8n.userman.de/webhook/botkonzept-registration', + // ... +}; +``` + +## 📊 Customer Journey + +### Tag 0: Registrierung + +1. **Kunde registriert sich** auf botkonzept.de +2. **n8n Webhook** empfängt Daten +3. **Validierung** der Eingaben +4. **Passwort generieren** (16 Zeichen) +5. **Kunde in DB speichern** (Supabase) +6. **LXC-Instanz erstellen** via `install.sh` +7. **Instanz-Daten speichern** in DB +8. **Willkommens-E-Mail** senden mit Zugangsdaten + +**E-Mail-Inhalt:** +- Dashboard-URL +- Login-Daten +- Chat-Webhook-URL +- Upload-Formular-URL +- Quick-Start-Guide + +### Tag 3: Frühbucher-Angebot + +**Automatisch um 9:00 Uhr:** +- **E-Mail:** "30% Frühbucher-Rabatt" +- **Preis:** €34,30/Monat (statt €49) +- **Ersparnis:** €176,40/Jahr +- **Gültigkeit:** 48 Stunden + +### Tag 5: Erinnerung + +**Automatisch um 9:00 Uhr:** +- **E-Mail:** "Nur noch 2 Tage - 15% Rabatt" +- **Preis:** €41,65/Monat (statt €49) +- **Ersparnis:** €88,20/Jahr +- **Warnung:** Instanz wird bald gelöscht + +### Tag 7: Letzte Chance + +**Automatisch um 9:00 Uhr:** +- **E-Mail:** "Trial endet heute" +- **Preis:** €49/Monat (Normalpreis) +- **Keine Rabatte** mehr verfügbar +- **Dringlichkeit:** Instanz wird um Mitternacht gelöscht + +### Tag 8: Instanz löschen + +**Automatisch um 9:00 Uhr:** +- **LXC-Instanz löschen** via `pct destroy` +- **Status aktualisieren** in DB +- **Goodbye-E-Mail** mit Feedback-Umfrage + +## 💰 Preis-Modell + +### Trial (7 Tage) +- **Preis:** €0 +- **Features:** Voller Funktionsumfang +- **Limit:** 100 Dokumente, 1.000 Nachrichten + +### Starter +- **Normalpreis:** €49/Monat +- **Tag 3 Rabatt:** €34,30/Monat (30% OFF) +- **Tag 5 Rabatt:** €41,65/Monat (15% OFF) +- **Features:** + - Unbegrenzte Dokumente + - 10.000 Nachrichten/Monat + - Prioritäts-Support + - Custom Branding + - Analytics Dashboard + +### Business +- **Preis:** €149/Monat +- **Features:** + - 50.000 Nachrichten/Monat + - Mehrere Chatbots + - API-Zugriff + - Dedizierter Support + - SLA-Garantie + +## 🔧 Technische Details + +### Datenbank-Schema + +**Haupttabellen:** +- `customers` - Kundendaten +- `instances` - LXC-Instanzen +- `subscriptions` - Abonnements +- `payments` - Zahlungen +- `emails_sent` - E-Mail-Tracking +- `usage_stats` - Nutzungsstatistiken +- `audit_log` - Audit-Trail + +### n8n Workflows + +#### 1. Customer Registration Workflow + +**Trigger:** Webhook (POST /webhook/botkonzept-registration) + +**Schritte:** +1. Validate Input +2. Generate Password & Trial Date +3. Create Customer in DB +4. Create Customer Instance (SSH) +5. Parse Install Output +6. Save Instance to DB +7. Send Welcome Email +8. Log Email Sent +9. Success Response + +#### 2. Trial Management Workflow + +**Trigger:** Cron (täglich 9:00 Uhr) + +**Schritte:** +1. Get Trial Customers (SQL Query) +2. Check Day 3/5/7/8 +3. Send entsprechende E-Mail +4. Log Email Sent +5. (Tag 8) Delete Instance + +### E-Mail-Templates + +Alle E-Mails sind: +- ✅ **Responsive** (Mobile-optimiert) +- ✅ **HTML-formatiert** mit Inline-CSS +- ✅ **Branded** mit Logo und Farben +- ✅ **CTA-optimiert** mit klaren Buttons +- ✅ **Tracking-fähig** (Opens, Clicks) + +### Security + +- ✅ **HTTPS** für alle Verbindungen +- ✅ **JWT-Tokens** für API-Authentifizierung +- ✅ **Row Level Security** in Supabase +- ✅ **Passwort-Hashing** (bcrypt) +- ✅ **DSGVO-konform** (Daten in DE) +- ✅ **Input-Validierung** auf allen Ebenen + +## 📧 E-Mail-Konfiguration + +### Postfix Gateway (OPNsense) + +```bash +# SMTP-Server: 192.168.45.1 +# Port: 25 (intern) +# Relay: Amazon SES +``` + +### Sendy.co Integration (optional) + +Für Newsletter und Marketing-E-Mails: + +```javascript +// In js/main.js +function subscribeNewsletter(email) { + const sendyUrl = 'https://sendy.userman.de/subscribe'; + // ... +} +``` + +## 💳 Payment-Integration + +### Stripe + +```javascript +// Stripe Checkout Session erstellen +const session = await stripe.checkout.sessions.create({ + customer_email: customer.email, + line_items: [{ + price: 'price_starter_monthly', + quantity: 1, + }], + mode: 'subscription', + success_url: 'https://botkonzept.de/success', + cancel_url: 'https://botkonzept.de/cancel', +}); +``` + +### PayPal + +```javascript +// PayPal Subscription erstellen +paypal.Buttons({ + createSubscription: function(data, actions) { + return actions.subscription.create({ + plan_id: 'P-STARTER-MONTHLY' + }); + } +}).render('#paypal-button-container'); +``` + +## 📈 Analytics & Tracking + +### Google Analytics + +```html + + +``` + +### Conversion Tracking + +```javascript +// In js/main.js +function trackConversion(eventName, data) { + gtag('event', eventName, { + 'event_category': 'registration', + 'event_label': 'trial', + 'value': 0 + }); +} +``` + +## 🧪 Testing + +### Lokales Testing + +```bash +# Website lokal testen +cd botkonzept-website +python3 -m http.server 8000 + +# n8n Workflow testen +curl -X POST https://n8n.userman.de/webhook/botkonzept-registration \ + -H "Content-Type: application/json" \ + -d '{ + "firstName": "Max", + "lastName": "Mustermann", + "email": "test@example.com", + "company": "Test GmbH" + }' +``` + +### Datenbank-Queries + +```sql +-- Alle Trial-Kunden anzeigen +SELECT * FROM customer_overview WHERE status = 'trial'; + +-- E-Mails der letzten 7 Tage +SELECT * FROM emails_sent WHERE sent_at >= NOW() - INTERVAL '7 days'; + +-- Trials die bald ablaufen +SELECT * FROM trials_expiring_soon; + +-- Revenue-Übersicht +SELECT * FROM revenue_metrics; +``` + +## 🔄 Workflow-Verbesserungen + +### Vorschläge für Erweiterungen + +1. **A/B Testing** + - Verschiedene E-Mail-Varianten testen + - Conversion-Rates vergleichen + +2. **Personalisierung** + - Branchen-spezifische E-Mails + - Nutzungsbasierte Empfehlungen + +3. **Retargeting** + - Abgebrochene Registrierungen + - Reaktivierung inaktiver Kunden + +4. **Referral-Programm** + - Kunden werben Kunden + - Rabatte für Empfehlungen + +5. **Upselling** + - Automatische Upgrade-Vorschläge + - Feature-basierte Empfehlungen + +## 📞 Support & Kontakt + +- **Website:** https://botkonzept.de +- **E-Mail:** support@botkonzept.de +- **Dokumentation:** https://docs.botkonzept.de +- **Status:** https://status.botkonzept.de + +## 📝 Lizenz + +Proprietär - Alle Rechte vorbehalten + +## 🎯 Roadmap + +### Q1 2025 +- [x] Website-Launch +- [x] Automatische Registrierung +- [x] Trial-Management +- [ ] Stripe-Integration +- [ ] PayPal-Integration + +### Q2 2025 +- [ ] Mobile App +- [ ] White-Label-Option +- [ ] API-Dokumentation +- [ ] Marketplace für Templates + +### Q3 2025 +- [ ] Multi-Language Support +- [ ] Advanced Analytics +- [ ] Team-Features +- [ ] Enterprise-Plan + +## 🙏 Credits + +Entwickelt mit: +- **n8n** - Workflow-Automation +- **Supabase** - Backend-as-a-Service +- **Proxmox** - Virtualisierung +- **PostgreSQL** - Datenbank +- **PostgREST** - REST API +- **Ollama** - LLM-Integration + +--- + +**Version:** 1.0.0 +**Letzte Aktualisierung:** 25.01.2025 +**Autor:** MediaMetz diff --git a/BotKonzept-Customer-Registration-Workflow.json b/BotKonzept-Customer-Registration-Workflow.json new file mode 100644 index 0000000..1a43454 --- /dev/null +++ b/BotKonzept-Customer-Registration-Workflow.json @@ -0,0 +1,312 @@ +{ + "name": "BotKonzept - Customer Registration & Trial Management", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "botkonzept-registration", + "responseMode": "responseNode", + "options": {} + }, + "id": "webhook-registration", + "name": "Registration Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [250, 300], + "webhookId": "botkonzept-registration" + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{$json.body.email}}", + "operation": "isNotEmpty" + }, + { + "value1": "={{$json.body.firstName}}", + "operation": "isNotEmpty" + }, + { + "value1": "={{$json.body.lastName}}", + "operation": "isNotEmpty" + } + ] + } + }, + "id": "validate-input", + "name": "Validate Input", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [450, 300] + }, + { + "parameters": { + "operation": "insert", + "schema": "public", + "table": "customers", + "columns": "email,first_name,last_name,company,status,created_at,trial_end_date", + "additionalFields": { + "returnFields": "*" + } + }, + "id": "create-customer", + "name": "Create Customer in DB", + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.4, + "position": [650, 200], + "credentials": { + "postgres": { + "id": "supabase-local", + "name": "Supabase Local" + } + } + }, + { + "parameters": { + "authentication": "privateKey", + "command": "=/root/customer-installer/install.sh --storage local-zfs --bridge vmbr0 --ip dhcp --vlan 90 --apt-proxy http://192.168.45.2:3142 --n8n-owner-email {{ $json.email }} --n8n-owner-pass \"{{ $('Generate-Password').item.json.password }}\"", + "cwd": "/root/customer-installer/" + }, + "id": "create-instance", + "name": "Create Customer Instance", + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [850, 200], + "credentials": { + "sshPrivateKey": { + "id": "pve20-ssh", + "name": "PVE20" + } + } + }, + { + "parameters": { + "jsCode": "// Parse installation output\nconst stdout = $input.item.json.stdout;\nconst installData = JSON.parse(stdout);\n\n// Add customer info\ninstallData.customer = {\n id: $('Create Customer in DB').item.json.id,\n email: $('Create Customer in DB').item.json.email,\n firstName: $('Create Customer in DB').item.json.first_name,\n lastName: $('Create Customer in DB').item.json.last_name,\n company: $('Create Customer in DB').item.json.company\n};\n\nreturn installData;" + }, + "id": "parse-install-output", + "name": "Parse Install Output", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1050, 200] + }, + { + "parameters": { + "operation": "insert", + "schema": "public", + "table": "instances", + "columns": "customer_id,ctid,hostname,ip,fqdn,status,credentials,created_at,trial_end_date", + "additionalFields": {} + }, + "id": "save-instance", + "name": "Save Instance to DB", + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.4, + "position": [1250, 200], + "credentials": { + "postgres": { + "id": "supabase-local", + "name": "Supabase Local" + } + } + }, + { + "parameters": { + "fromEmail": "noreply@botkonzept.de", + "toEmail": "={{ $json.customer.email }}", + "subject": "Willkommen bei BotKonzept - Ihre Instanz ist bereit! 🎉", + "emailType": "html", + "message": "=\n\n\n \n \n\n\n
\n
\n

🎉 Willkommen bei BotKonzept!

\n

Ihre KI-Chatbot-Instanz ist bereit

\n
\n \n
\n

Hallo {{ $json.customer.firstName }},

\n \n

vielen Dank für Ihre Registrierung! Ihre persönliche KI-Chatbot-Instanz wurde erfolgreich erstellt und ist jetzt einsatzbereit.

\n \n
\n

📋 Ihre Zugangsdaten

\n

Dashboard-URL:
\n {{ $json.urls.n8n_external }}

\n \n

E-Mail: {{ $json.n8n.owner_email }}
\n Passwort: {{ $json.n8n.owner_password }}

\n \n

Chat-Webhook:
\n {{ $json.urls.chat_webhook }}

\n \n

Upload-Formular:
\n {{ $json.urls.upload_form }}

\n
\n \n

🚀 Nächste Schritte:

\n
    \n
  1. Einloggen: Klicken Sie auf den Link oben und loggen Sie sich ein
  2. \n
  3. Dokumente hochladen: Laden Sie Ihre PDFs, FAQs oder andere Dokumente hoch
  4. \n
  5. Chatbot testen: Testen Sie Ihren Chatbot direkt im Dashboard
  6. \n
  7. Code einbinden: Kopieren Sie den Widget-Code auf Ihre Website
  8. \n
\n \n Jetzt Dashboard öffnen →\n \n
\n

💰 Frühbucher-Angebot:

\n

Upgraden Sie in den nächsten 3 Tagen und erhalten Sie 30% Rabatt auf Ihr erstes Jahr!

\n
\n \n

Trial-Zeitraum: 7 Tage (bis {{ $json.trial_end_date }})

\n \n

Bei Fragen stehen wir Ihnen jederzeit zur Verfügung!

\n \n

Viel Erfolg mit Ihrem KI-Chatbot!
\n Ihr BotKonzept-Team

\n
\n \n
\n

BotKonzept | KI-Chatbots für moderne Unternehmen

\n

botkonzept.de | support@botkonzept.de

\n
\n
\n\n", + "options": { + "allowUnauthorizedCerts": false + } + }, + "id": "send-welcome-email", + "name": "Send Welcome Email", + "type": "n8n-nodes-base.emailSend", + "typeVersion": 2.1, + "position": [1450, 200], + "credentials": { + "smtp": { + "id": "postfix-ses", + "name": "Postfix SES" + } + } + }, + { + "parameters": { + "operation": "insert", + "schema": "public", + "table": "emails_sent", + "columns": "customer_id,email_type,sent_at", + "additionalFields": {} + }, + "id": "log-email", + "name": "Log Email Sent", + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.4, + "position": [1650, 200], + "credentials": { + "postgres": { + "id": "supabase-local", + "name": "Supabase Local" + } + } + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { \"success\": true, \"message\": \"Registrierung erfolgreich! Sie erhalten in Kürze eine E-Mail mit Ihren Zugangsdaten.\", \"customerId\": $json.customer.id, \"instanceUrl\": $json.urls.n8n_external } }}", + "options": { + "responseCode": 200 + } + }, + "id": "success-response", + "name": "Success Response", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [1850, 200] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ { \"success\": false, \"error\": \"Ungültige Eingabedaten. Bitte überprüfen Sie Ihre Angaben.\" } }}", + "options": { + "responseCode": 400 + } + }, + "id": "error-response", + "name": "Error Response", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [650, 400] + }, + { + "parameters": { + "jsCode": "// Generate secure password\nconst length = 16;\nconst charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\nlet password = '';\n\nfor (let i = 0; i < length; i++) {\n const randomIndex = Math.floor(Math.random() * charset.length);\n password += charset[randomIndex];\n}\n\n// Calculate trial end date (7 days from now)\nconst trialEndDate = new Date();\ntrialEndDate.setDate(trialEndDate.getDate() + 7);\n\nreturn {\n password: password,\n trialEndDate: trialEndDate.toISOString(),\n email: $json.body.email,\n firstName: $json.body.firstName,\n lastName: $json.body.lastName,\n company: $json.body.company || null\n};" + }, + "id": "generate-password", + "name": "Generate Password & Trial Date", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [650, 100] + } + ], + "connections": { + "Registration Webhook": { + "main": [ + [ + { + "node": "Validate Input", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validate Input": { + "main": [ + [ + { + "node": "Generate Password & Trial Date", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Error Response", + "type": "main", + "index": 0 + } + ] + ] + }, + "Generate Password & Trial Date": { + "main": [ + [ + { + "node": "Create Customer in DB", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create Customer in DB": { + "main": [ + [ + { + "node": "Create Customer Instance", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create Customer Instance": { + "main": [ + [ + { + "node": "Parse Install Output", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Install Output": { + "main": [ + [ + { + "node": "Save Instance to DB", + "type": "main", + "index": 0 + } + ] + ] + }, + "Save Instance to DB": { + "main": [ + [ + { + "node": "Send Welcome Email", + "type": "main", + "index": 0 + } + ] + ] + }, + "Send Welcome Email": { + "main": [ + [ + { + "node": "Log Email Sent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Log Email Sent": { + "main": [ + [ + { + "node": "Success Response", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [], + "triggerCount": 0, + "updatedAt": "2025-01-25T00:00:00.000Z", + "versionId": "1" +} diff --git a/BotKonzept-Trial-Management-Workflow.json b/BotKonzept-Trial-Management-Workflow.json new file mode 100644 index 0000000..b154f6a --- /dev/null +++ b/BotKonzept-Trial-Management-Workflow.json @@ -0,0 +1,122 @@ +{ + "name": "BotKonzept - Trial Management & Email Automation", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 9 * * *" + } + ] + } + }, + "id": "daily-cron", + "name": "Daily at 9 AM", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.1, + "position": [250, 300] + }, + { + "parameters": { + "operation": "executeQuery", + "query": "SELECT c.id as customer_id, c.email, c.first_name, c.last_name, c.company, c.created_at, c.status, i.ctid, i.hostname, i.fqdn, i.trial_end_date, i.credentials, EXTRACT(DAY FROM (NOW() - c.created_at)) as days_since_registration FROM customers c JOIN instances i ON c.id = i.customer_id WHERE c.status = 'trial' AND i.status = 'active' AND c.created_at >= NOW() - INTERVAL '8 days'", + "additionalFields": {} + }, + "id": "get-trial-customers", + "name": "Get Trial Customers", + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.4, + "position": [450, 300], + "credentials": { + "postgres": { + "id": "supabase-local", + "name": "Supabase Local" + } + } + }, + { + "parameters": { + "conditions": { + "number": [ + { + "value1": "={{$json.days_since_registration}}", + "operation": "equal", + "value2": 3 + } + ] + } + }, + "id": "check-day-3", + "name": "Day 3?", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [650, 200] + }, + { + "parameters": { + "operation": "insert", + "schema": "public", + "table": "emails_sent", + "columns": "customer_id,email_type,sent_at", + "additionalFields": {} + }, + "id": "log-email-sent", + "name": "Log Email Sent", + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.4, + "position": [1450, 200], + "credentials": { + "postgres": { + "id": "supabase-local", + "name": "Supabase Local" + } + } + } + ], + "connections": { + "Daily at 9 AM": { + "main": [ + [ + { + "node": "Get Trial Customers", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Trial Customers": { + "main": [ + [ + { + "node": "Day 3?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Day 3?": { + "main": [ + [ + { + "node": "Log Email Sent", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [], + "triggerCount": 0, + "updatedAt": "2025-01-25T00:00:00.000Z", + "versionId": "1" +} diff --git a/DEPLOYMENT_CHECKLIST.md b/DEPLOYMENT_CHECKLIST.md new file mode 100644 index 0000000..312e228 --- /dev/null +++ b/DEPLOYMENT_CHECKLIST.md @@ -0,0 +1,363 @@ +# 🚀 BotKonzept - Deployment Checkliste + +## ✅ Pre-Deployment + +### Infrastruktur +- [ ] Proxmox VE20 läuft und ist erreichbar +- [ ] Supabase PostgreSQL ist konfiguriert +- [ ] n8n Instanz ist verfügbar +- [ ] OPNsense NGINX Reverse Proxy ist konfiguriert +- [ ] Postfix/SES E-Mail-Gateway funktioniert +- [ ] DNS für botkonzept.de ist konfiguriert + +### Datenbank +- [ ] PostgreSQL-Verbindung getestet +- [ ] Schema `botkonzept_schema.sql` importiert +- [ ] Tabellen erstellt (customers, instances, etc.) +- [ ] Views erstellt (customer_overview, trials_expiring_soon) +- [ ] Row Level Security aktiviert +- [ ] Backup-Strategie definiert + +### n8n Workflows +- [ ] Customer Registration Workflow importiert +- [ ] Trial Management Workflow importiert +- [ ] SSH-Credentials (PVE20) konfiguriert +- [ ] PostgreSQL-Credentials konfiguriert +- [ ] SMTP-Credentials konfiguriert +- [ ] Webhooks aktiviert +- [ ] Cron-Jobs aktiviert (täglich 9:00 Uhr) + +### Website +- [ ] HTML/CSS/JS-Dateien geprüft +- [ ] Logo (20250119_Logo_Botkozept.svg) vorhanden +- [ ] Webhook-URL in main.js konfiguriert +- [ ] SSL-Zertifikat installiert +- [ ] HTTPS erzwungen +- [ ] Cookie-Banner implementiert +- [ ] Datenschutzerklärung vorhanden +- [ ] Impressum vorhanden +- [ ] AGB vorhanden + +## 🔧 Deployment Steps + +### 1. Datenbank Setup + +```bash +# Verbindung testen +psql -h 192.168.45.3 -U customer -d customer -c "SELECT 1" + +# Schema importieren +psql -h 192.168.45.3 -U customer -d customer -f sql/botkonzept_schema.sql + +# Tabellen verifizieren +psql -h 192.168.45.3 -U customer -d customer -c "\dt" +``` + +**Erwartetes Ergebnis:** +- 7 Tabellen erstellt +- 3 Views erstellt +- Triggers aktiv + +### 2. n8n Workflows + +```bash +# 1. n8n öffnen +open https://n8n.userman.de + +# 2. Workflows importieren +# - BotKonzept-Customer-Registration-Workflow.json +# - BotKonzept-Trial-Management-Workflow.json + +# 3. Credentials konfigurieren +# SSH (PVE20): /root/.ssh/id_rsa +# PostgreSQL: 192.168.45.3:5432/customer +# SMTP: Postfix Gateway +``` + +**Webhook-URLs:** +- Registration: `https://n8n.userman.de/webhook/botkonzept-registration` +- Test: `curl -X POST https://n8n.userman.de/webhook/botkonzept-registration -H "Content-Type: application/json" -d '{"test":true}'` + +### 3. Website Deployment + +```bash +# Setup-Script ausführen +chmod +x setup_botkonzept.sh +./setup_botkonzept.sh + +# Oder manuell: +sudo mkdir -p /var/www/botkonzept +sudo cp -r botkonzept-website/* /var/www/botkonzept/ +sudo chown -R www-data:www-data /var/www/botkonzept +``` + +**NGINX-Konfiguration:** + +```nginx +server { + listen 80; + server_name botkonzept.de www.botkonzept.de; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name botkonzept.de www.botkonzept.de; + + ssl_certificate /etc/letsencrypt/live/botkonzept.de/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/botkonzept.de/privkey.pem; + + root /var/www/botkonzept; + index index.html; + + location / { + try_files $uri $uri/ =404; + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; +} +``` + +### 4. SSL-Zertifikat + +```bash +# Let's Encrypt installieren +sudo apt-get install certbot python3-certbot-nginx + +# Zertifikat erstellen +sudo certbot --nginx -d botkonzept.de -d www.botkonzept.de + +# Auto-Renewal testen +sudo certbot renew --dry-run +``` + +## ✅ Post-Deployment Tests + +### 1. Datenbank-Tests + +```sql +-- Kunden-Tabelle testen +INSERT INTO customers (email, first_name, last_name, status) +VALUES ('test@example.com', 'Test', 'User', 'trial') +RETURNING *; + +-- View testen +SELECT * FROM customer_overview; + +-- Cleanup +DELETE FROM customers WHERE email = 'test@example.com'; +``` + +### 2. Workflow-Tests + +```bash +# Registration Webhook testen +curl -X POST https://n8n.userman.de/webhook/botkonzept-registration \ + -H "Content-Type: application/json" \ + -d '{ + "firstName": "Max", + "lastName": "Mustermann", + "email": "test@example.com", + "company": "Test GmbH", + "terms": true + }' + +# Erwartete Antwort: +# {"success": true, "message": "Registrierung erfolgreich!"} +``` + +### 3. Website-Tests + +- [ ] Homepage lädt (https://botkonzept.de) +- [ ] Alle Bilder werden angezeigt +- [ ] Navigation funktioniert +- [ ] Formular wird angezeigt +- [ ] Formular-Validierung funktioniert +- [ ] Mobile-Ansicht korrekt +- [ ] SSL-Zertifikat gültig +- [ ] Keine Console-Errors + +### 4. E-Mail-Tests + +```bash +# Test-E-Mail senden +echo "Test" | mail -s "BotKonzept Test" test@example.com + +# Postfix-Logs prüfen +tail -f /var/log/mail.log +``` + +### 5. End-to-End Test + +1. **Registrierung:** + - [ ] Formular ausfüllen + - [ ] Absenden + - [ ] Success-Message erscheint + +2. **Datenbank:** + - [ ] Kunde in `customers` Tabelle + - [ ] Instanz in `instances` Tabelle + - [ ] E-Mail in `emails_sent` Tabelle + +3. **E-Mail:** + - [ ] Willkommens-E-Mail erhalten + - [ ] Zugangsdaten korrekt + - [ ] Links funktionieren + +4. **Instanz:** + - [ ] LXC erstellt (pct list) + - [ ] n8n erreichbar + - [ ] Login funktioniert + +## 📊 Monitoring + +### Datenbank-Monitoring + +```sql +-- Aktive Trials +SELECT COUNT(*) FROM customers WHERE status = 'trial'; + +-- Trials die heute ablaufen +SELECT * FROM trials_expiring_soon WHERE days_remaining < 1; + +-- E-Mails der letzten 24h +SELECT email_type, COUNT(*) +FROM emails_sent +WHERE sent_at >= NOW() - INTERVAL '24 hours' +GROUP BY email_type; + +-- Revenue heute +SELECT SUM(amount) FROM payments +WHERE status = 'succeeded' +AND paid_at::date = CURRENT_DATE; +``` + +### n8n-Monitoring + +- [ ] Workflow-Executions prüfen +- [ ] Error-Rate überwachen +- [ ] Execution-Time tracken + +### Server-Monitoring + +```bash +# LXC-Container zählen +pct list | grep -c "running" + +# Disk-Usage +df -h + +# Memory-Usage +free -h + +# Load Average +uptime +``` + +## 🔒 Security Checklist + +- [ ] Firewall-Regeln konfiguriert +- [ ] SSH nur mit Key-Auth +- [ ] PostgreSQL nur intern erreichbar +- [ ] n8n hinter Reverse Proxy +- [ ] SSL/TLS erzwungen +- [ ] Rate-Limiting aktiviert +- [ ] CORS korrekt konfiguriert +- [ ] Input-Validierung aktiv +- [ ] SQL-Injection-Schutz +- [ ] XSS-Schutz +- [ ] CSRF-Schutz + +## 📝 Backup-Strategie + +### Datenbank-Backup + +```bash +# Tägliches Backup +0 2 * * * pg_dump -h 192.168.45.3 -U customer customer > /backup/botkonzept_$(date +\%Y\%m\%d).sql + +# Backup-Retention (30 Tage) +find /backup -name "botkonzept_*.sql" -mtime +30 -delete +``` + +### LXC-Backup + +```bash +# Proxmox Backup +vzdump --mode snapshot --compress gzip --storage backup-storage +``` + +### Website-Backup + +```bash +# Git-Repository +cd /var/www/botkonzept +git init +git add . +git commit -m "Website backup $(date)" +git push origin main +``` + +## 🚨 Rollback-Plan + +### Bei Problemen mit Workflows + +1. Workflows deaktivieren +2. Alte Version wiederherstellen +3. Credentials prüfen +4. Neu aktivieren + +### Bei Datenbank-Problemen + +```bash +# Backup wiederherstellen +psql -h 192.168.45.3 -U customer customer < /backup/botkonzept_YYYYMMDD.sql +``` + +### Bei Website-Problemen + +```bash +# Alte Version wiederherstellen +git checkout HEAD~1 +sudo cp -r botkonzept-website/* /var/www/botkonzept/ +``` + +## 📞 Support-Kontakte + +- **Proxmox:** admin@userman.de +- **n8n:** support@userman.de +- **DNS:** dns@userman.de +- **E-Mail:** postmaster@userman.de + +## ✅ Go-Live Checklist + +- [ ] Alle Tests bestanden +- [ ] Monitoring aktiv +- [ ] Backups konfiguriert +- [ ] Team informiert +- [ ] Dokumentation aktuell +- [ ] Support-Prozesse definiert +- [ ] Rollback-Plan getestet +- [ ] Performance-Tests durchgeführt +- [ ] Security-Audit durchgeführt +- [ ] DSGVO-Compliance geprüft + +## 🎉 Post-Launch + +- [ ] Analytics einrichten (Google Analytics) +- [ ] Conversion-Tracking aktivieren +- [ ] A/B-Tests planen +- [ ] Marketing-Kampagnen starten +- [ ] Social Media ankündigen +- [ ] Blog-Post veröffentlichen +- [ ] Newsletter versenden + +--- + +**Deployment-Datum:** _________________ +**Deployed von:** _________________ +**Version:** 1.0.0 +**Status:** ⬜ In Arbeit | ⬜ Bereit | ⬜ Live diff --git a/Install-Workflow01.json b/Install-Workflow01.json new file mode 100644 index 0000000..bc9d44e --- /dev/null +++ b/Install-Workflow01.json @@ -0,0 +1,326 @@ +{ + "name": "Install-Workflow", + "nodes": [ + { + "parameters": { + "authentication": "privateKey", + "command": "/root/customer-installer/install.sh --storage local-zfs --bridge vmbr0 --ip dhcp --vlan 90 --apt-proxy http://192.168.45.2:3142", + "cwd": "/root/customer-installer/" + }, + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + 192, + 0 + ], + "id": "9878a864-651e-4a53-abfc-b0bc2d2f2d15", + "name": "Start Installer 1", + "credentials": { + "sshPrivateKey": { + "id": "bPl4nGXO7Iz4wJPJ", + "name": "PVE20" + } + } + }, + { + "parameters": { + "authentication": "privateKey", + "command": "=./n8n_setup.sh --n8n_internal {{ $json.urls.n8n_internal }} --owner_email {{ $json.n8n.owner_email }} --owner_password \"{{ $json.n8n.owner_password }}\" --owner_first_name \"Max\" --owner_last_name \"Mustermann\" --timeout 30", + "cwd": "/root/customer-installer/" + }, + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + 640, + 0 + ], + "id": "62e1b647-7daa-445e-bf99-873efbe2bc94", + "name": "Setup n8n Owner", + "credentials": { + "sshPrivateKey": { + "id": "bPl4nGXO7Iz4wJPJ", + "name": "PVE20" + } + }, + "disabled": true + }, + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -32, + 0 + ], + "id": "11aa3e6a-b20a-49b0-b6f9-4cec469c9916", + "name": "When clicking ‘Execute workflow’" + }, + { + "parameters": { + "authentication": "privateKey", + "command": "=bash setup_nginx_proxy.sh --ctid {{ $('install_JSON').item.json.ctid }} --hostname {{ $('install_JSON').item.json.hostname }} --fqdn {{ $('install_JSON').item.json.fqdn }} --backend-ip {{ $('install_JSON').item.json.ip }}", + "cwd": "/root/customer-installer/" + }, + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + 1088, + 0 + ], + "id": "176fd78c-9dd6-427b-a566-88dbf504347e", + "name": "setup_nginx_proxy", + "credentials": { + "sshPrivateKey": { + "id": "bPl4nGXO7Iz4wJPJ", + "name": "PVE20" + } + }, + "disabled": true + }, + { + "parameters": { + "formTitle": "Delete", + "formFields": { + "values": [ + { + "fieldLabel": "Host-Number", + "requiredField": true + } + ] + }, + "options": { + "buttonLabel": "Löschen" + } + }, + "type": "n8n-nodes-base.formTrigger", + "typeVersion": 2.5, + "position": [ + -32, + 208 + ], + "id": "4c9878ec-177c-4fbd-a83a-a3895ebd8193", + "name": "On form submission", + "webhookId": "12847d69-15c8-44e4-b595-645ff3109355" + }, + { + "parameters": { + "jsCode": "// Code Node - JavaScript\nconst sshOutput = $input.all();\n\n// stdout-String parsen\nconst parsedData = JSON.parse(sshOutput[0].json.stdout);\n\nreturn [{\n json: parsedData\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 416, + 0 + ], + "id": "67ace9d6-7ed2-4495-b3e6-6091fd252596", + "name": "install_JSON" + }, + { + "parameters": { + "jsCode": "// Code Node - JavaScript\nconst sshOutput = $input.all();\n\n// stdout-String parsen\nconst parsedData = JSON.parse(sshOutput[0].json.stdout);\n\nreturn [{\n json: parsedData\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 864, + 0 + ], + "id": "fc1b9d9f-5a70-476e-8ae2-4cfd81050527", + "name": "setupOwnerJSON", + "disabled": true + }, + { + "parameters": { + "jsCode": "// Code Node - JavaScript\nconst sshOutput = $input.all();\n\n// stdout-String parsen\nconst parsedData = JSON.parse(sshOutput[0].json.stdout);\n\nreturn [{\n json: parsedData\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1312, + 0 + ], + "id": "86b70266-cafe-4a4f-98d1-20434f61061b", + "name": "setupProxyJSON", + "disabled": true + }, + { + "parameters": { + "chatId": "26805429", + "text": "=Done.\nhttps://{{ $json.fqdn }}\nUsername: {{ $('install_JSON').item.json.n8n.owner_email }}\nPassword: {{ $('install_JSON').item.json.n8n.owner_password }}\nChat: {{ $('install_JSON').item.json.urls.chat_webhook }}", + "additionalFields": { + "appendAttribution": false + } + }, + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.2, + "position": [ + 1536, + 0 + ], + "id": "9e20ee75-c2ae-421f-ac4c-54f5643032f2", + "name": "Send a text message", + "webhookId": "55a97e93-a457-4bee-8b8f-3eea1ad62928", + "credentials": { + "telegramApi": { + "id": "7DYJfIx5JsxgKc0D", + "name": "LizTheLove_Bot" + } + } + }, + { + "parameters": { + "authentication": "privateKey", + "command": "=/root/customer-installer/delete_nginx_proxy.sh --ctid {{ $('On form submission').item.json['Host-Number'] }}", + "cwd": "/root/customer-installer/" + }, + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + 416, + 208 + ], + "id": "34d50d3a-cdb8-4801-b54b-719ef60b4b63", + "name": "deleteProxy", + "credentials": { + "sshPrivateKey": { + "id": "bPl4nGXO7Iz4wJPJ", + "name": "PVE20" + } + } + }, + { + "parameters": { + "authentication": "privateKey", + "command": "=pct stop {{ $json['Host-Number'] }} && pct destroy {{ $json['Host-Number'] }} ", + "cwd": "/root/customer-installer/" + }, + "type": "n8n-nodes-base.ssh", + "typeVersion": 1, + "position": [ + 192, + 208 + ], + "id": "4645ace7-30ef-4698-8288-065fe70ff9b0", + "name": "deleteLXC", + "credentials": { + "sshPrivateKey": { + "id": "bPl4nGXO7Iz4wJPJ", + "name": "PVE20" + } + } + } + ], + "pinData": {}, + "connections": { + "Start Installer 1": { + "main": [ + [ + { + "node": "install_JSON", + "type": "main", + "index": 0 + } + ] + ] + }, + "Setup n8n Owner": { + "main": [ + [ + { + "node": "setupOwnerJSON", + "type": "main", + "index": 0 + } + ] + ] + }, + "When clicking ‘Execute workflow’": { + "main": [ + [ + { + "node": "Start Installer 1", + "type": "main", + "index": 0 + } + ] + ] + }, + "On form submission": { + "main": [ + [ + { + "node": "deleteLXC", + "type": "main", + "index": 0 + } + ] + ] + }, + "install_JSON": { + "main": [ + [ + { + "node": "Setup n8n Owner", + "type": "main", + "index": 0 + } + ] + ] + }, + "setupOwnerJSON": { + "main": [ + [ + { + "node": "setup_nginx_proxy", + "type": "main", + "index": 0 + } + ] + ] + }, + "setup_nginx_proxy": { + "main": [ + [ + { + "node": "setupProxyJSON", + "type": "main", + "index": 0 + } + ] + ] + }, + "setupProxyJSON": { + "main": [ + [ + { + "node": "Send a text message", + "type": "main", + "index": 0 + } + ] + ] + }, + "deleteLXC": { + "main": [ + [ + { + "node": "deleteProxy", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "availableInMCP": false + }, + "versionId": "7814a0dd-fae3-40e6-913b-f7b8bac10e81", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "c832ea07b2e6ccb9d5832b81bb497b720b31ed50006898070951c4f6d209f426" + }, + "id": "ba7BxTeeQif4zu2F", + "tags": [] +} \ No newline at end of file diff --git a/botkonzept-website/css/style.css b/botkonzept-website/css/style.css new file mode 100644 index 0000000..bbd6c6a --- /dev/null +++ b/botkonzept-website/css/style.css @@ -0,0 +1,884 @@ +/* =================================== + CSS Variables & Reset + =================================== */ +:root { + --primary-color: #2563eb; + --primary-dark: #1e40af; + --primary-light: #3b82f6; + --secondary-color: #10b981; + --text-dark: #1f2937; + --text-light: #6b7280; + --bg-light: #f9fafb; + --bg-white: #ffffff; + --border-color: #e5e7eb; + --success-color: #10b981; + --error-color: #ef4444; + --warning-color: #f59e0b; + + --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + --font-mono: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--font-sans); + color: var(--text-dark); + line-height: 1.6; + background-color: var(--bg-white); +} + +img { + max-width: 100%; + height: auto; +} + +a { + text-decoration: none; + color: inherit; + transition: var(--transition); +} + +ul { + list-style: none; +} + +/* =================================== + Container & Layout + =================================== */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 1.5rem; +} + +section { + padding: 5rem 0; +} + +.section-header { + text-align: center; + margin-bottom: 3rem; +} + +.section-header h2 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 1rem; + color: var(--text-dark); +} + +.section-header p { + font-size: 1.25rem; + color: var(--text-light); + max-width: 600px; + margin: 0 auto; +} + +/* =================================== + Navigation + =================================== */ +.navbar { + background: var(--bg-white); + box-shadow: var(--shadow-sm); + position: sticky; + top: 0; + z-index: 1000; + border-bottom: 1px solid var(--border-color); +} + +.nav-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 0; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 1.5rem; + font-weight: 700; + color: var(--text-dark); +} + +.logo img { + height: 40px; +} + +.nav-menu { + display: flex; + align-items: center; + gap: 2rem; +} + +.nav-menu a { + color: var(--text-light); + font-weight: 500; + transition: var(--transition); +} + +.nav-menu a:hover { + color: var(--primary-color); +} + +.mobile-menu-toggle { + display: none; + flex-direction: column; + gap: 0.25rem; + background: none; + border: none; + cursor: pointer; + padding: 0.5rem; +} + +.mobile-menu-toggle span { + width: 24px; + height: 2px; + background: var(--text-dark); + transition: var(--transition); +} + +/* =================================== + Buttons + =================================== */ +.btn-primary, +.btn-secondary { + display: inline-block; + padding: 0.75rem 1.5rem; + border-radius: var(--radius-md); + font-weight: 600; + text-align: center; + transition: var(--transition); + cursor: pointer; + border: none; + font-size: 1rem; +} + +.btn-primary { + background: var(--primary-color); + color: white; +} + +.btn-primary:hover { + background: var(--primary-dark); + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.btn-secondary { + background: transparent; + color: var(--primary-color); + border: 2px solid var(--primary-color); +} + +.btn-secondary:hover { + background: var(--primary-color); + color: white; +} + +.btn-large { + padding: 1rem 2rem; + font-size: 1.125rem; +} + +.btn-block { + width: 100%; + display: block; +} + +/* =================================== + Hero Section + =================================== */ +.hero { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 6rem 0; + position: relative; + overflow: hidden; +} + +.hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: url('data:image/svg+xml,'); + opacity: 0.3; +} + +.hero-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + align-items: center; + position: relative; + z-index: 1; +} + +.hero h1 { + font-size: 3.5rem; + font-weight: 800; + line-height: 1.2; + margin-bottom: 1.5rem; +} + +.hero-subtitle { + font-size: 1.25rem; + margin-bottom: 2rem; + opacity: 0.95; +} + +.hero-cta { + display: flex; + gap: 1rem; + margin-bottom: 2rem; +} + +.hero-features { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.feature-badge { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.95rem; + opacity: 0.9; +} + +.feature-badge svg { + flex-shrink: 0; +} + +/* Chatbot Demo */ +.chatbot-demo { + background: white; + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + overflow: hidden; + max-width: 400px; +} + +.demo-header { + background: var(--primary-color); + color: white; + padding: 1rem; + display: flex; + align-items: center; + gap: 1rem; +} + +.demo-dots { + display: flex; + gap: 0.5rem; +} + +.demo-dots span { + width: 12px; + height: 12px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); +} + +.demo-title { + font-weight: 600; +} + +.demo-messages { + padding: 1.5rem; + background: #f9fafb; + min-height: 300px; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.message { + display: flex; + gap: 0.75rem; + align-items: flex-start; +} + +.message.user { + flex-direction: row-reverse; +} + +.message .avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--primary-light); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.message .bubble { + background: white; + padding: 0.75rem 1rem; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); + max-width: 70%; + color: var(--text-dark); +} + +.message.user .bubble { + background: var(--primary-color); + color: white; +} + +.demo-input { + padding: 1rem; + background: white; + border-top: 1px solid var(--border-color); + display: flex; + gap: 0.5rem; +} + +.demo-input input { + flex: 1; + padding: 0.75rem; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + font-size: 0.95rem; +} + +.demo-input button { + padding: 0.75rem 1.5rem; + background: var(--primary-color); + color: white; + border: none; + border-radius: var(--radius-md); + cursor: pointer; + font-size: 1.25rem; +} + +/* =================================== + Features Section + =================================== */ +.features { + background: var(--bg-light); +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; +} + +.feature-card { + background: white; + padding: 2rem; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + transition: var(--transition); +} + +.feature-card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-xl); +} + +.feature-icon { + font-size: 3rem; + margin-bottom: 1rem; +} + +.feature-card h3 { + font-size: 1.5rem; + margin-bottom: 1rem; + color: var(--text-dark); +} + +.feature-card p { + color: var(--text-light); + line-height: 1.7; +} + +/* =================================== + How It Works Section + =================================== */ +.steps { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + margin-top: 3rem; +} + +.step { + text-align: center; + position: relative; +} + +.step-number { + width: 60px; + height: 60px; + background: var(--primary-color); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + font-weight: 700; + margin: 0 auto 1.5rem; +} + +.step h3 { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.step p { + color: var(--text-light); +} + +/* =================================== + Pricing Section + =================================== */ +.pricing { + background: var(--bg-light); +} + +.pricing-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + margin-top: 3rem; +} + +.pricing-card { + background: white; + border-radius: var(--radius-xl); + padding: 2.5rem; + box-shadow: var(--shadow-md); + transition: var(--transition); + position: relative; +} + +.pricing-card:hover { + transform: translateY(-5px); + box-shadow: var(--shadow-xl); +} + +.pricing-card.featured { + border: 3px solid var(--primary-color); + transform: scale(1.05); +} + +.pricing-card .badge { + position: absolute; + top: -15px; + right: 20px; + background: var(--primary-color); + color: white; + padding: 0.5rem 1rem; + border-radius: var(--radius-md); + font-weight: 600; + font-size: 0.875rem; +} + +.pricing-header h3 { + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.price { + display: flex; + align-items: baseline; + gap: 0.25rem; + margin-bottom: 0.5rem; +} + +.price .currency { + font-size: 1.5rem; + font-weight: 600; +} + +.price .amount { + font-size: 3.5rem; + font-weight: 800; + color: var(--primary-color); +} + +.price .period { + font-size: 1.125rem; + color: var(--text-light); +} + +.price-note { + font-size: 0.875rem; + color: var(--success-color); + font-weight: 600; + margin-bottom: 1.5rem; +} + +.pricing-features { + margin: 2rem 0; +} + +.pricing-features li { + padding: 0.75rem 0; + border-bottom: 1px solid var(--border-color); + color: var(--text-light); +} + +.pricing-features li:last-child { + border-bottom: none; +} + +/* =================================== + Trial Registration Section + =================================== */ +.trial-registration { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.trial-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + align-items: center; +} + +.trial-info h2 { + font-size: 2.5rem; + margin-bottom: 1rem; +} + +.trial-info p { + font-size: 1.25rem; + margin-bottom: 2rem; + opacity: 0.95; +} + +.trial-benefits { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.trial-benefits li { + display: flex; + align-items: center; + gap: 1rem; + font-size: 1.125rem; +} + +.trial-benefits svg { + flex-shrink: 0; + color: var(--success-color); +} + +.trial-form-wrapper { + background: white; + border-radius: var(--radius-xl); + padding: 2.5rem; + box-shadow: var(--shadow-xl); +} + +.trial-form h3 { + color: var(--text-dark); + font-size: 1.75rem; + margin-bottom: 1.5rem; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; + color: var(--text-dark); + font-weight: 500; +} + +.form-group input { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + font-size: 1rem; + transition: var(--transition); +} + +.form-group input:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.form-group.checkbox { + display: flex; + align-items: flex-start; + gap: 0.75rem; +} + +.form-group.checkbox input { + width: auto; + margin-top: 0.25rem; +} + +.form-group.checkbox label { + margin: 0; + font-weight: 400; + font-size: 0.95rem; +} + +.form-group.checkbox a { + color: var(--primary-color); + text-decoration: underline; +} + +.form-note { + font-size: 0.875rem; + color: var(--text-light); + text-align: center; + margin-top: 1rem; +} + +.btn-loading { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.spinner { + animation: spin 1s linear infinite; +} + +.spinner circle { + stroke-dasharray: 90, 150; + stroke-dashoffset: 0; + stroke-linecap: round; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } +} + +/* Success Message */ +.success-message { + text-align: center; + padding: 2rem; +} + +.success-icon { + width: 80px; + height: 80px; + background: var(--success-color); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 3rem; + margin: 0 auto 1.5rem; +} + +.success-message h3 { + color: var(--text-dark); + font-size: 1.75rem; + margin-bottom: 1rem; +} + +.success-message p { + color: var(--text-light); + margin-bottom: 1.5rem; +} + +.success-details { + background: var(--bg-light); + padding: 1.5rem; + border-radius: var(--radius-md); + text-align: left; +} + +.success-details p { + color: var(--text-dark); + font-weight: 600; + margin-bottom: 1rem; +} + +.success-details ol { + list-style: decimal; + padding-left: 1.5rem; + color: var(--text-light); +} + +.success-details li { + margin-bottom: 0.5rem; +} + +/* =================================== + FAQ Section + =================================== */ +.faq-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 2rem; +} + +.faq-item { + background: white; + padding: 2rem; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); +} + +.faq-item h3 { + font-size: 1.25rem; + margin-bottom: 1rem; + color: var(--text-dark); +} + +.faq-item p { + color: var(--text-light); + line-height: 1.7; +} + +/* =================================== + Footer + =================================== */ +.footer { + background: var(--text-dark); + color: white; + padding: 3rem 0 1.5rem; +} + +.footer-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 2rem; + margin-bottom: 2rem; +} + +.footer-section h4 { + margin-bottom: 1rem; + font-size: 1.125rem; +} + +.footer-section p { + opacity: 0.8; + margin-top: 0.5rem; +} + +.footer-section ul { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.footer-section a { + opacity: 0.8; + transition: var(--transition); +} + +.footer-section a:hover { + opacity: 1; + color: var(--primary-light); +} + +.footer-bottom { + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: 1.5rem; + text-align: center; + opacity: 0.8; +} + +/* =================================== + Responsive Design + =================================== */ +@media (max-width: 768px) { + .nav-menu { + display: none; + } + + .mobile-menu-toggle { + display: flex; + } + + .hero-content, + .trial-content { + grid-template-columns: 1fr; + gap: 2rem; + } + + .hero h1 { + font-size: 2.5rem; + } + + .hero-cta { + flex-direction: column; + } + + .section-header h2 { + font-size: 2rem; + } + + .steps { + grid-template-columns: 1fr; + } + + .pricing-grid { + grid-template-columns: 1fr; + } + + .pricing-card.featured { + transform: scale(1); + } + + .faq-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 480px) { + .hero h1 { + font-size: 2rem; + } + + .hero-subtitle { + font-size: 1rem; + } + + .section-header h2 { + font-size: 1.75rem; + } + + .trial-form-wrapper { + padding: 1.5rem; + } +} diff --git a/botkonzept-website/index.html b/botkonzept-website/index.html new file mode 100644 index 0000000..18983d6 --- /dev/null +++ b/botkonzept-website/index.html @@ -0,0 +1,417 @@ + + + + + + BotKonzept - KI-Chatbot für Ihre Website in 7 Tagen kostenlos testen + + + + + + + + + +
+
+
+
+

Ihr KI-Chatbot in Minuten einsatzbereit

+

+ Verwandeln Sie Ihre Dokumente in einen intelligenten Chatbot. + Keine Programmierung erforderlich. 7 Tage kostenlos testen. +

+ +
+
+ + + + Keine Kreditkarte erforderlich +
+
+ + + + In 5 Minuten eingerichtet +
+
+ + + + DSGVO-konform +
+
+
+
+
+
+
+ + + +
+ Ihr Chatbot +
+
+
+
🤖
+
Hallo! Wie kann ich Ihnen helfen?
+
+
+
Was sind Ihre Öffnungszeiten?
+
👤
+
+
+
🤖
+
Wir sind Mo-Fr von 9-18 Uhr für Sie da!
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+

Leistungsstarke Features für Ihren Erfolg

+

Alles, was Sie für einen professionellen KI-Chatbot benötigen

+
+
+
+
📄
+

Dokument-Upload

+

Laden Sie PDFs, Word-Dokumente oder Texte hoch. Ihr Chatbot lernt automatisch aus Ihren Inhalten.

+
+
+
🧠
+

RAG-Technologie

+

Modernste Retrieval-Augmented Generation für präzise und kontextbezogene Antworten.

+
+
+
+

Blitzschnelle Integration

+

Ein einfacher Code-Snippet - fertig! Ihr Chatbot ist in Minuten auf Ihrer Website.

+
+
+
🎨
+

Anpassbares Design

+

Passen Sie Farben, Logo und Texte an Ihre Marke an. Kein Entwickler erforderlich.

+
+
+
📊
+

Analytics & Insights

+

Verstehen Sie Ihre Kunden besser mit detaillierten Statistiken und Gesprächsverläufen.

+
+
+
🔒
+

DSGVO-konform

+

Ihre Daten bleiben in Deutschland. Volle DSGVO-Konformität garantiert.

+
+
+
+
+ + +
+
+
+

So einfach geht's

+

In 4 Schritten zu Ihrem eigenen KI-Chatbot

+
+
+
+
1
+
+

Registrieren

+

Erstellen Sie Ihr kostenloses Testkonto in wenigen Sekunden. Keine Kreditkarte erforderlich.

+
+
+
+
2
+
+

Dokumente hochladen

+

Laden Sie Ihre PDFs, FAQs oder andere Dokumente hoch. Unser System verarbeitet sie automatisch.

+
+
+
+
3
+
+

Code einbinden

+

Kopieren Sie den generierten Code-Snippet und fügen Sie ihn auf Ihrer Website ein.

+
+
+
+
4
+
+

Fertig!

+

Ihr KI-Chatbot ist live und beantwortet Kundenanfragen rund um die Uhr.

+
+
+
+
+
+ + +
+
+
+

Transparente Preise

+

Starten Sie kostenlos, upgraden Sie jederzeit

+
+
+
+
+

Trial

+
+ + 0 + /7 Tage +
+
+
    +
  • ✓ Voller Funktionsumfang
  • +
  • ✓ Bis zu 100 Dokumente
  • +
  • ✓ 1.000 Nachrichten/Monat
  • +
  • ✓ E-Mail-Support
  • +
  • ✓ Keine Kreditkarte erforderlich
  • +
+ Jetzt starten +
+ +
+
+

Business

+
+ + 149 + /Monat +
+
+
    +
  • ✓ Alle Starter-Features
  • +
  • ✓ 50.000 Nachrichten/Monat
  • +
  • ✓ Mehrere Chatbots
  • +
  • ✓ API-Zugriff
  • +
  • ✓ Dedizierter Support
  • +
  • ✓ SLA-Garantie
  • +
+ Kontakt aufnehmen +
+
+
+
+ + +
+
+
+
+

Starten Sie Ihren 7-Tage-Test

+

Keine Kreditkarte erforderlich. Voller Funktionsumfang. Jederzeit kündbar.

+
    +
  • + + + + Sofortiger Zugang zu Ihrer Instanz +
  • +
  • + + + + Persönliche Einrichtungs-Hilfe +
  • +
  • + + + + Exklusive Frühbucher-Rabatte +
  • +
+
+
+
+

Kostenlos registrieren

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +

Nach der Registrierung erhalten Sie sofort Zugang zu Ihrer persönlichen Instanz.

+
+ +
+
+
+
+ + +
+
+
+

Häufig gestellte Fragen

+

Alles, was Sie wissen müssen

+
+
+
+

Brauche ich technische Kenntnisse?

+

Nein! Sie müssen nur einen Code-Snippet auf Ihrer Website einfügen können. Alles andere übernehmen wir für Sie.

+
+
+

Wie lange dauert die Einrichtung?

+

Nach der Registrierung ist Ihre Instanz in ca. 5 Minuten einsatzbereit. Das Hochladen von Dokumenten dauert je nach Menge 1-10 Minuten.

+
+
+

Kann ich jederzeit kündigen?

+

Ja, Sie können jederzeit kündigen. Es gibt keine Mindestlaufzeit und keine versteckten Kosten.

+
+
+

Wo werden meine Daten gespeichert?

+

Alle Daten werden in Deutschland auf DSGVO-konformen Servern gespeichert. Ihre Daten gehören Ihnen.

+
+
+

Welche Sprachen werden unterstützt?

+

Der Chatbot unterstützt Deutsch, Englisch und viele weitere Sprachen. Die KI erkennt die Sprache automatisch.

+
+
+

Was passiert nach dem Trial?

+

Sie erhalten am Tag 3, 5 und 7 E-Mails mit Upgrade-Angeboten. Wenn Sie nicht upgraden, wird Ihre Instanz nach 7 Tagen gelöscht.

+
+
+
+
+ + + + + + + diff --git a/botkonzept-website/js/main.js b/botkonzept-website/js/main.js new file mode 100644 index 0000000..f00e875 --- /dev/null +++ b/botkonzept-website/js/main.js @@ -0,0 +1,388 @@ +// BotKonzept Website - Main JavaScript +// ===================================== + +// Configuration +const CONFIG = { + // n8n Webhook URL für Registrierung (wird später konfiguriert) + WEBHOOK_URL: 'https://n8n.userman.de/webhook/botkonzept-registration', + // Supabase URL (lokale Instanz) + SUPABASE_URL: 'http://192.168.45.3:3000', + SUPABASE_ANON_KEY: 'your-anon-key-here' +}; + +// ===================================== +// Mobile Menu Toggle +// ===================================== +document.addEventListener('DOMContentLoaded', function() { + const mobileMenuToggle = document.querySelector('.mobile-menu-toggle'); + const navMenu = document.querySelector('.nav-menu'); + + if (mobileMenuToggle) { + mobileMenuToggle.addEventListener('click', function() { + navMenu.classList.toggle('active'); + this.classList.toggle('active'); + }); + } + + // Close mobile menu when clicking on a link + const navLinks = document.querySelectorAll('.nav-menu a'); + navLinks.forEach(link => { + link.addEventListener('click', function() { + navMenu.classList.remove('active'); + mobileMenuToggle.classList.remove('active'); + }); + }); +}); + +// ===================================== +// Smooth Scroll for Anchor Links +// ===================================== +document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + const offset = 80; // Navbar height + const targetPosition = target.offsetTop - offset; + window.scrollTo({ + top: targetPosition, + behavior: 'smooth' + }); + } + }); +}); + +// ===================================== +// Trial Registration Form +// ===================================== +const trialForm = document.getElementById('trialForm'); +const successMessage = document.getElementById('successMessage'); + +if (trialForm) { + trialForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + // Get form data + const formData = { + firstName: document.getElementById('firstName').value.trim(), + lastName: document.getElementById('lastName').value.trim(), + email: document.getElementById('email').value.trim().toLowerCase(), + company: document.getElementById('company').value.trim() || null, + terms: document.getElementById('terms').checked, + registeredAt: new Date().toISOString(), + source: 'website', + utmSource: getUrlParameter('utm_source') || null, + utmMedium: getUrlParameter('utm_medium') || null, + utmCampaign: getUrlParameter('utm_campaign') || null + }; + + // Validate + if (!formData.firstName || !formData.lastName || !formData.email) { + showError('Bitte füllen Sie alle Pflichtfelder aus.'); + return; + } + + if (!validateEmail(formData.email)) { + showError('Bitte geben Sie eine gültige E-Mail-Adresse ein.'); + return; + } + + if (!formData.terms) { + showError('Bitte akzeptieren Sie die AGB und Datenschutzerklärung.'); + return; + } + + // Show loading state + const submitButton = trialForm.querySelector('button[type="submit"]'); + const btnText = submitButton.querySelector('.btn-text'); + const btnLoading = submitButton.querySelector('.btn-loading'); + + submitButton.disabled = true; + btnText.style.display = 'none'; + btnLoading.style.display = 'flex'; + + try { + // Send to n8n webhook + const response = await fetch(CONFIG.WEBHOOK_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData) + }); + + if (!response.ok) { + throw new Error('Registrierung fehlgeschlagen'); + } + + const result = await response.json(); + + // Show success message + trialForm.style.display = 'none'; + successMessage.style.display = 'block'; + + // Track conversion (Google Analytics, Facebook Pixel, etc.) + trackConversion('trial_registration', formData); + + // Scroll to success message + successMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }); + + } catch (error) { + console.error('Registration error:', error); + showError('Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut oder kontaktieren Sie uns direkt.'); + + // Reset button state + submitButton.disabled = false; + btnText.style.display = 'inline'; + btnLoading.style.display = 'none'; + } + }); +} + +// ===================================== +// Helper Functions +// ===================================== + +function validateEmail(email) { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(email); +} + +function showError(message) { + // Create or update error message + let errorDiv = document.querySelector('.form-error'); + + if (!errorDiv) { + errorDiv = document.createElement('div'); + errorDiv.className = 'form-error'; + errorDiv.style.cssText = ` + background: #fee2e2; + border: 1px solid #ef4444; + color: #991b1b; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1rem; + font-size: 0.95rem; + `; + trialForm.insertBefore(errorDiv, trialForm.firstChild); + } + + errorDiv.textContent = message; + errorDiv.scrollIntoView({ behavior: 'smooth', block: 'center' }); + + // Remove after 5 seconds + setTimeout(() => { + errorDiv.remove(); + }, 5000); +} + +function getUrlParameter(name) { + const urlParams = new URLSearchParams(window.location.search); + return urlParams.get(name); +} + +function trackConversion(eventName, data) { + // Google Analytics + if (typeof gtag !== 'undefined') { + gtag('event', eventName, { + 'event_category': 'registration', + 'event_label': 'trial', + 'value': 0 + }); + } + + // Facebook Pixel + if (typeof fbq !== 'undefined') { + fbq('track', 'Lead', { + content_name: 'Trial Registration', + content_category: 'Registration' + }); + } + + // Custom tracking + console.log('Conversion tracked:', eventName, data); +} + +// ===================================== +// Scroll Animations +// ===================================== +const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' +}; + +const observer = new IntersectionObserver(function(entries) { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animate-in'); + observer.unobserve(entry.target); + } + }); +}, observerOptions); + +// Observe elements for animation +document.addEventListener('DOMContentLoaded', function() { + const animateElements = document.querySelectorAll('.feature-card, .step, .pricing-card, .faq-item'); + animateElements.forEach(el => { + el.style.opacity = '0'; + el.style.transform = 'translateY(20px)'; + el.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; + observer.observe(el); + }); +}); + +// Add CSS for animation +const style = document.createElement('style'); +style.textContent = ` + .animate-in { + opacity: 1 !important; + transform: translateY(0) !important; + } +`; +document.head.appendChild(style); + +// ===================================== +// Pricing Calculator (Optional) +// ===================================== +function calculatePricing(plan, discount = 0) { + const prices = { + trial: 0, + starter: 49, + business: 149 + }; + + const basePrice = prices[plan] || 0; + const discountedPrice = basePrice * (1 - discount / 100); + + return { + basePrice, + discount, + discountedPrice, + savings: basePrice - discountedPrice + }; +} + +// ===================================== +// FAQ Accordion (Optional Enhancement) +// ===================================== +document.querySelectorAll('.faq-item').forEach(item => { + item.addEventListener('click', function() { + this.classList.toggle('active'); + }); +}); + +// ===================================== +// Newsletter Subscription (Optional) +// ===================================== +function subscribeNewsletter(email) { + // Integration mit Sendy.co + const sendyUrl = 'https://sendy.userman.de/subscribe'; + + const formData = new FormData(); + formData.append('email', email); + formData.append('list', 'your-list-id'); + formData.append('boolean', 'true'); + + return fetch(sendyUrl, { + method: 'POST', + body: formData + }); +} + +// ===================================== +// Live Chat Widget Integration (Optional) +// ===================================== +function initializeChatWidget() { + // Hier kann später der eigene BotKonzept-Chatbot integriert werden + console.log('Chat widget initialized'); +} + +// ===================================== +// A/B Testing Helper (Optional) +// ===================================== +function getABTestVariant() { + const variants = ['A', 'B']; + const stored = localStorage.getItem('ab_variant'); + + if (stored) { + return stored; + } + + const variant = variants[Math.floor(Math.random() * variants.length)]; + localStorage.setItem('ab_variant', variant); + return variant; +} + +// ===================================== +// Cookie Consent (DSGVO) +// ===================================== +function showCookieConsent() { + const consent = localStorage.getItem('cookie_consent'); + + if (!consent) { + const banner = document.createElement('div'); + banner.className = 'cookie-banner'; + banner.innerHTML = ` + + `; + banner.style.cssText = ` + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #1f2937; + color: white; + padding: 1.5rem; + z-index: 9999; + box-shadow: 0 -4px 6px rgba(0,0,0,0.1); + `; + document.body.appendChild(banner); + } +} + +function acceptCookies() { + localStorage.setItem('cookie_consent', 'true'); + document.querySelector('.cookie-banner').remove(); +} + +// Show cookie consent on page load +document.addEventListener('DOMContentLoaded', showCookieConsent); + +// ===================================== +// Performance Monitoring +// ===================================== +if ('PerformanceObserver' in window) { + const observer = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + console.log('Performance:', entry.name, entry.duration); + } + }); + observer.observe({ entryTypes: ['measure', 'navigation'] }); +} + +// ===================================== +// Error Tracking +// ===================================== +window.addEventListener('error', function(e) { + console.error('Global error:', e.error); + // Hier könnte Sentry oder ähnliches integriert werden +}); + +window.addEventListener('unhandledrejection', function(e) { + console.error('Unhandled promise rejection:', e.reason); +}); + +// ===================================== +// Export for testing +// ===================================== +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + validateEmail, + calculatePricing, + getUrlParameter + }; +} diff --git a/setup_botkonzept.sh b/setup_botkonzept.sh new file mode 100755 index 0000000..8c2ccde --- /dev/null +++ b/setup_botkonzept.sh @@ -0,0 +1,298 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# ===================================================== +# BotKonzept Setup Script +# ===================================================== +# This script sets up the complete BotKonzept platform +# including database, n8n workflows, and website + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +SUPABASE_HOST="${SUPABASE_HOST:-192.168.45.3}" +SUPABASE_PORT="${SUPABASE_PORT:-5432}" +SUPABASE_DB="${SUPABASE_DB:-customer}" +SUPABASE_USER="${SUPABASE_USER:-customer}" +SUPABASE_PASSWORD="${SUPABASE_PASSWORD:-}" + +N8N_HOST="${N8N_HOST:-n8n.userman.de}" +WEBSITE_DIR="${WEBSITE_DIR:-/var/www/botkonzept}" + +print_header() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" +} + +print_success() { + echo -e "${GREEN}✓${NC} $1" +} + +print_error() { + echo -e "${RED}✗${NC} $1" +} + +print_info() { + echo -e "${BLUE}ℹ${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +# Check prerequisites +check_prerequisites() { + print_header "Checking Prerequisites" + + local missing=0 + + # Check psql + if command -v psql >/dev/null 2>&1; then + print_success "PostgreSQL client installed" + else + print_error "PostgreSQL client not found" + missing=1 + fi + + # Check curl + if command -v curl >/dev/null 2>&1; then + print_success "curl installed" + else + print_error "curl not found" + missing=1 + fi + + # Check jq + if command -v jq >/dev/null 2>&1; then + print_success "jq installed" + else + print_warning "jq not found (optional, but recommended)" + fi + + if [[ $missing -eq 1 ]]; then + print_error "Missing required dependencies. Please install them first." + exit 1 + fi + + echo "" +} + +# Setup database +setup_database() { + print_header "Setting up Database" + + if [[ -z "$SUPABASE_PASSWORD" ]]; then + print_error "SUPABASE_PASSWORD not set" + echo "Please set the password:" + echo " export SUPABASE_PASSWORD='your-password'" + exit 1 + fi + + print_info "Connecting to PostgreSQL at ${SUPABASE_HOST}:${SUPABASE_PORT}" + + # Test connection + if PGPASSWORD="$SUPABASE_PASSWORD" psql -h "$SUPABASE_HOST" -p "$SUPABASE_PORT" -U "$SUPABASE_USER" -d "$SUPABASE_DB" -c "SELECT 1" >/dev/null 2>&1; then + print_success "Database connection successful" + else + print_error "Cannot connect to database" + exit 1 + fi + + # Create schema + print_info "Creating BotKonzept schema..." + if PGPASSWORD="$SUPABASE_PASSWORD" psql -h "$SUPABASE_HOST" -p "$SUPABASE_PORT" -U "$SUPABASE_USER" -d "$SUPABASE_DB" -f "${SCRIPT_DIR}/sql/botkonzept_schema.sql" >/dev/null 2>&1; then + print_success "Schema created successfully" + else + print_error "Failed to create schema" + exit 1 + fi + + # Verify tables + print_info "Verifying tables..." + local tables=$(PGPASSWORD="$SUPABASE_PASSWORD" psql -h "$SUPABASE_HOST" -p "$SUPABASE_PORT" -U "$SUPABASE_USER" -d "$SUPABASE_DB" -tAc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_name IN ('customers', 'instances', 'subscriptions', 'payments', 'emails_sent')") + + if [[ "$tables" -eq 5 ]]; then + print_success "All tables created successfully" + else + print_warning "Expected 5 tables, found $tables" + fi + + echo "" +} + +# Setup n8n workflows +setup_n8n_workflows() { + print_header "Setting up n8n Workflows" + + print_info "n8n workflows need to be imported manually:" + echo "" + echo "1. Open n8n: https://${N8N_HOST}" + echo "2. Go to Workflows → Import from File" + echo "3. Import these files:" + echo " - ${SCRIPT_DIR}/BotKonzept-Customer-Registration-Workflow.json" + echo " - ${SCRIPT_DIR}/BotKonzept-Trial-Management-Workflow.json" + echo "" + echo "4. Configure credentials:" + echo " - SSH (PVE20): Private key for Proxmox" + echo " - PostgreSQL (Supabase): Host=${SUPABASE_HOST}, DB=${SUPABASE_DB}" + echo " - SMTP (Postfix/SES): Your email server" + echo "" + + read -p "Press Enter when workflows are imported and configured..." + + print_success "n8n workflows configured" + echo "" +} + +# Setup website +setup_website() { + print_header "Setting up Website" + + if [[ ! -d "${SCRIPT_DIR}/botkonzept-website" ]]; then + print_error "Website directory not found: ${SCRIPT_DIR}/botkonzept-website" + exit 1 + fi + + print_info "Website files location: ${SCRIPT_DIR}/botkonzept-website" + print_info "Target directory: ${WEBSITE_DIR}" + echo "" + + # Update webhook URL in JavaScript + print_info "Updating webhook URL in JavaScript..." + local webhook_url="https://${N8N_HOST}/webhook/botkonzept-registration" + + if [[ -f "${SCRIPT_DIR}/botkonzept-website/js/main.js" ]]; then + sed -i.bak "s|WEBHOOK_URL:.*|WEBHOOK_URL: '${webhook_url}',|" "${SCRIPT_DIR}/botkonzept-website/js/main.js" + print_success "Webhook URL updated to: ${webhook_url}" + fi + + echo "" + print_info "To deploy the website, run:" + echo " sudo mkdir -p ${WEBSITE_DIR}" + echo " sudo cp -r ${SCRIPT_DIR}/botkonzept-website/* ${WEBSITE_DIR}/" + echo " sudo chown -R www-data:www-data ${WEBSITE_DIR}" + echo "" + + read -p "Deploy website now? (y/N): " deploy + if [[ "$deploy" =~ ^[Yy]$ ]]; then + sudo mkdir -p "${WEBSITE_DIR}" + sudo cp -r "${SCRIPT_DIR}/botkonzept-website/"* "${WEBSITE_DIR}/" + sudo chown -R www-data:www-data "${WEBSITE_DIR}" + print_success "Website deployed to ${WEBSITE_DIR}" + else + print_info "Skipping website deployment" + fi + + echo "" +} + +# Test setup +test_setup() { + print_header "Testing Setup" + + # Test database + print_info "Testing database connection..." + if PGPASSWORD="$SUPABASE_PASSWORD" psql -h "$SUPABASE_HOST" -p "$SUPABASE_PORT" -U "$SUPABASE_USER" -d "$SUPABASE_DB" -c "SELECT COUNT(*) FROM customers" >/dev/null 2>&1; then + print_success "Database accessible" + else + print_error "Database not accessible" + fi + + # Test n8n webhook + print_info "Testing n8n webhook..." + local webhook_url="https://${N8N_HOST}/webhook/botkonzept-registration" + local response=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$webhook_url" \ + -H "Content-Type: application/json" \ + -d '{"test": true}' 2>/dev/null || echo "000") + + if [[ "$response" == "200" ]] || [[ "$response" == "400" ]]; then + print_success "n8n webhook accessible (HTTP $response)" + else + print_warning "n8n webhook returned HTTP $response" + fi + + # Test website + if [[ -d "$WEBSITE_DIR" ]]; then + print_success "Website directory exists" + else + print_warning "Website not deployed yet" + fi + + echo "" +} + +# Generate summary +generate_summary() { + print_header "Setup Summary" + + echo -e "${GREEN}✓ BotKonzept setup completed!${NC}" + echo "" + echo "Next steps:" + echo "" + echo "1. Configure DNS:" + echo " - Point botkonzept.de to your web server" + echo " - Configure SSL certificate (Let's Encrypt)" + echo "" + echo "2. Test registration:" + echo " - Visit https://botkonzept.de" + echo " - Fill out registration form" + echo " - Check email for welcome message" + echo "" + echo "3. Monitor workflows:" + echo " - n8n: https://${N8N_HOST}" + echo " - Check executions for errors" + echo "" + echo "4. Database access:" + echo " - Host: ${SUPABASE_HOST}" + echo " - Database: ${SUPABASE_DB}" + echo " - User: ${SUPABASE_USER}" + echo "" + echo "5. Useful queries:" + echo " - View customers: SELECT * FROM customer_overview;" + echo " - View trials: SELECT * FROM trials_expiring_soon;" + echo " - View emails: SELECT * FROM emails_sent ORDER BY sent_at DESC LIMIT 10;" + echo "" + echo "Documentation: ${SCRIPT_DIR}/BOTKONZEPT_README.md" + echo "" +} + +# Main execution +main() { + clear + print_header "BotKonzept Setup" + + echo "This script will set up the complete BotKonzept platform." + echo "" + echo "Components:" + echo " - PostgreSQL database schema" + echo " - n8n workflows (manual import)" + echo " - Website files" + echo "" + + read -p "Continue? (y/N): " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "Setup cancelled." + exit 0 + fi + + echo "" + + check_prerequisites + setup_database + setup_n8n_workflows + setup_website + test_setup + generate_summary +} + +# Run main function +main "$@" diff --git a/sql/botkonzept_schema.sql b/sql/botkonzept_schema.sql new file mode 100644 index 0000000..0330350 --- /dev/null +++ b/sql/botkonzept_schema.sql @@ -0,0 +1,444 @@ +-- ===================================================== +-- BotKonzept - Database Schema for Customer Management +-- ===================================================== +-- This schema manages customers, instances, emails, and payments +-- for the BotKonzept SaaS platform + +-- Enable UUID extension +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- ===================================================== +-- Table: customers +-- ===================================================== +-- Stores customer information and trial status +CREATE TABLE IF NOT EXISTS customers ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + email VARCHAR(255) UNIQUE NOT NULL, + first_name VARCHAR(100) NOT NULL, + last_name VARCHAR(100) NOT NULL, + company VARCHAR(255), + phone VARCHAR(50), + + -- Status tracking + status VARCHAR(50) DEFAULT 'trial' CHECK (status IN ('trial', 'active', 'cancelled', 'suspended', 'deleted')), + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + trial_end_date TIMESTAMPTZ, + subscription_start_date TIMESTAMPTZ, + subscription_end_date TIMESTAMPTZ, + + -- Marketing tracking + utm_source VARCHAR(100), + utm_medium VARCHAR(100), + utm_campaign VARCHAR(100), + referral_code VARCHAR(50), + + -- Metadata + metadata JSONB DEFAULT '{}'::jsonb, + + -- Indexes + CONSTRAINT email_format CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') +); + +-- Create indexes for customers +CREATE INDEX idx_customers_email ON customers(email); +CREATE INDEX idx_customers_status ON customers(status); +CREATE INDEX idx_customers_created_at ON customers(created_at); +CREATE INDEX idx_customers_trial_end_date ON customers(trial_end_date); + +-- ===================================================== +-- Table: instances +-- ===================================================== +-- Stores LXC instance information for each customer +CREATE TABLE IF NOT EXISTS instances ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + + -- Instance details + ctid BIGINT NOT NULL UNIQUE, + hostname VARCHAR(255) NOT NULL, + ip VARCHAR(50) NOT NULL, + fqdn VARCHAR(255) NOT NULL, + vlan INTEGER, + + -- Status + status VARCHAR(50) DEFAULT 'active' CHECK (status IN ('creating', 'active', 'suspended', 'deleted', 'error')), + + -- Credentials (encrypted JSON) + credentials JSONB NOT NULL, + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + trial_end_date TIMESTAMPTZ, + + -- Resource usage + disk_usage_gb DECIMAL(10,2), + memory_usage_mb INTEGER, + cpu_usage_percent DECIMAL(5,2), + + -- Metadata + metadata JSONB DEFAULT '{}'::jsonb +); + +-- Create indexes for instances +CREATE INDEX idx_instances_customer_id ON instances(customer_id); +CREATE INDEX idx_instances_ctid ON instances(ctid); +CREATE INDEX idx_instances_status ON instances(status); +CREATE INDEX idx_instances_hostname ON instances(hostname); + +-- ===================================================== +-- Table: emails_sent +-- ===================================================== +-- Tracks all emails sent to customers +CREATE TABLE IF NOT EXISTS emails_sent ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + + -- Email details + email_type VARCHAR(50) NOT NULL CHECK (email_type IN ( + 'welcome', + 'day3_upgrade', + 'day5_reminder', + 'day7_last_chance', + 'day8_goodbye', + 'payment_confirm', + 'payment_failed', + 'instance_created', + 'instance_deleted', + 'password_reset', + 'newsletter' + )), + + subject VARCHAR(255), + recipient_email VARCHAR(255) NOT NULL, + + -- Status + status VARCHAR(50) DEFAULT 'sent' CHECK (status IN ('sent', 'delivered', 'opened', 'clicked', 'bounced', 'failed')), + + -- Timestamps + sent_at TIMESTAMPTZ DEFAULT NOW(), + delivered_at TIMESTAMPTZ, + opened_at TIMESTAMPTZ, + clicked_at TIMESTAMPTZ, + + -- Metadata + metadata JSONB DEFAULT '{}'::jsonb +); + +-- Create indexes for emails_sent +CREATE INDEX idx_emails_customer_id ON emails_sent(customer_id); +CREATE INDEX idx_emails_type ON emails_sent(email_type); +CREATE INDEX idx_emails_sent_at ON emails_sent(sent_at); +CREATE INDEX idx_emails_status ON emails_sent(status); + +-- ===================================================== +-- Table: subscriptions +-- ===================================================== +-- Stores subscription and payment information +CREATE TABLE IF NOT EXISTS subscriptions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + + -- Plan details + plan_name VARCHAR(50) NOT NULL CHECK (plan_name IN ('trial', 'starter', 'business', 'enterprise')), + plan_price DECIMAL(10,2) NOT NULL, + billing_cycle VARCHAR(20) DEFAULT 'monthly' CHECK (billing_cycle IN ('monthly', 'yearly')), + + -- Discount + discount_percent DECIMAL(5,2) DEFAULT 0, + discount_code VARCHAR(50), + discount_end_date TIMESTAMPTZ, + + -- Status + status VARCHAR(50) DEFAULT 'active' CHECK (status IN ('active', 'cancelled', 'past_due', 'suspended')), + + -- Payment provider + payment_provider VARCHAR(50) CHECK (payment_provider IN ('stripe', 'paypal', 'manual')), + payment_provider_id VARCHAR(255), + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + current_period_start TIMESTAMPTZ, + current_period_end TIMESTAMPTZ, + cancelled_at TIMESTAMPTZ, + + -- Metadata + metadata JSONB DEFAULT '{}'::jsonb +); + +-- Create indexes for subscriptions +CREATE INDEX idx_subscriptions_customer_id ON subscriptions(customer_id); +CREATE INDEX idx_subscriptions_status ON subscriptions(status); +CREATE INDEX idx_subscriptions_plan_name ON subscriptions(plan_name); + +-- ===================================================== +-- Table: payments +-- ===================================================== +-- Stores payment transaction history +CREATE TABLE IF NOT EXISTS payments ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE, + subscription_id UUID REFERENCES subscriptions(id) ON DELETE SET NULL, + + -- Payment details + amount DECIMAL(10,2) NOT NULL, + currency VARCHAR(3) DEFAULT 'EUR', + + -- Status + status VARCHAR(50) DEFAULT 'pending' CHECK (status IN ('pending', 'succeeded', 'failed', 'refunded', 'cancelled')), + + -- Payment provider + payment_provider VARCHAR(50) CHECK (payment_provider IN ('stripe', 'paypal', 'manual')), + payment_provider_id VARCHAR(255), + payment_method VARCHAR(50), + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + paid_at TIMESTAMPTZ, + refunded_at TIMESTAMPTZ, + + -- Invoice + invoice_number VARCHAR(50), + invoice_url TEXT, + + -- Metadata + metadata JSONB DEFAULT '{}'::jsonb +); + +-- Create indexes for payments +CREATE INDEX idx_payments_customer_id ON payments(customer_id); +CREATE INDEX idx_payments_subscription_id ON payments(subscription_id); +CREATE INDEX idx_payments_status ON payments(status); +CREATE INDEX idx_payments_created_at ON payments(created_at); + +-- ===================================================== +-- Table: usage_stats +-- ===================================================== +-- Tracks usage statistics for each instance +CREATE TABLE IF NOT EXISTS usage_stats ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + instance_id UUID NOT NULL REFERENCES instances(id) ON DELETE CASCADE, + + -- Usage metrics + date DATE NOT NULL, + messages_count INTEGER DEFAULT 0, + documents_count INTEGER DEFAULT 0, + api_calls_count INTEGER DEFAULT 0, + storage_used_mb DECIMAL(10,2) DEFAULT 0, + + -- Timestamps + created_at TIMESTAMPTZ DEFAULT NOW(), + + -- Unique constraint: one record per instance per day + UNIQUE(instance_id, date) +); + +-- Create indexes for usage_stats +CREATE INDEX idx_usage_instance_id ON usage_stats(instance_id); +CREATE INDEX idx_usage_date ON usage_stats(date); + +-- ===================================================== +-- Table: audit_log +-- ===================================================== +-- Audit trail for important actions +CREATE TABLE IF NOT EXISTS audit_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + customer_id UUID REFERENCES customers(id) ON DELETE SET NULL, + instance_id UUID REFERENCES instances(id) ON DELETE SET NULL, + + -- Action details + action VARCHAR(100) NOT NULL, + entity_type VARCHAR(50), + entity_id UUID, + + -- User/system that performed the action + performed_by VARCHAR(100), + ip_address INET, + user_agent TEXT, + + -- Changes + old_values JSONB, + new_values JSONB, + + -- Timestamp + created_at TIMESTAMPTZ DEFAULT NOW(), + + -- Metadata + metadata JSONB DEFAULT '{}'::jsonb +); + +-- Create indexes for audit_log +CREATE INDEX idx_audit_customer_id ON audit_log(customer_id); +CREATE INDEX idx_audit_instance_id ON audit_log(instance_id); +CREATE INDEX idx_audit_action ON audit_log(action); +CREATE INDEX idx_audit_created_at ON audit_log(created_at); + +-- ===================================================== +-- Functions & Triggers +-- ===================================================== + +-- Function to update updated_at timestamp +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Triggers for updated_at +CREATE TRIGGER update_customers_updated_at BEFORE UPDATE ON customers + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER update_instances_updated_at BEFORE UPDATE ON instances + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER update_subscriptions_updated_at BEFORE UPDATE ON subscriptions + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +-- Function to calculate trial end date +CREATE OR REPLACE FUNCTION set_trial_end_date() +RETURNS TRIGGER AS $$ +BEGIN + IF NEW.trial_end_date IS NULL THEN + NEW.trial_end_date = NEW.created_at + INTERVAL '7 days'; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Trigger for trial end date +CREATE TRIGGER set_customer_trial_end_date BEFORE INSERT ON customers + FOR EACH ROW EXECUTE FUNCTION set_trial_end_date(); + +-- ===================================================== +-- Views +-- ===================================================== + +-- View: Active trials expiring soon +CREATE OR REPLACE VIEW trials_expiring_soon AS +SELECT + c.id, + c.email, + c.first_name, + c.last_name, + c.created_at, + c.trial_end_date, + EXTRACT(DAY FROM (c.trial_end_date - NOW())) as days_remaining, + i.ctid, + i.hostname, + i.fqdn +FROM customers c +JOIN instances i ON c.id = i.customer_id +WHERE c.status = 'trial' + AND i.status = 'active' + AND c.trial_end_date > NOW() + AND c.trial_end_date <= NOW() + INTERVAL '3 days'; + +-- View: Customer overview with instance info +CREATE OR REPLACE VIEW customer_overview AS +SELECT + c.id, + c.email, + c.first_name, + c.last_name, + c.company, + c.status, + c.created_at, + c.trial_end_date, + i.ctid, + i.hostname, + i.fqdn, + i.ip, + i.status as instance_status, + s.plan_name, + s.plan_price, + s.status as subscription_status +FROM customers c +LEFT JOIN instances i ON c.id = i.customer_id AND i.status = 'active' +LEFT JOIN subscriptions s ON c.id = s.customer_id AND s.status = 'active'; + +-- View: Revenue metrics +CREATE OR REPLACE VIEW revenue_metrics AS +SELECT + DATE_TRUNC('month', paid_at) as month, + COUNT(*) as payment_count, + SUM(amount) as total_revenue, + AVG(amount) as average_payment, + COUNT(DISTINCT customer_id) as unique_customers +FROM payments +WHERE status = 'succeeded' + AND paid_at IS NOT NULL +GROUP BY DATE_TRUNC('month', paid_at) +ORDER BY month DESC; + +-- ===================================================== +-- Row Level Security (RLS) Policies +-- ===================================================== + +-- Enable RLS on tables +ALTER TABLE customers ENABLE ROW LEVEL SECURITY; +ALTER TABLE instances ENABLE ROW LEVEL SECURITY; +ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY; +ALTER TABLE payments ENABLE ROW LEVEL SECURITY; + +-- Policy: Customers can only see their own data +CREATE POLICY customers_select_own ON customers + FOR SELECT + USING (auth.uid()::text = id::text); + +CREATE POLICY instances_select_own ON instances + FOR SELECT + USING (customer_id::text = auth.uid()::text); + +CREATE POLICY subscriptions_select_own ON subscriptions + FOR SELECT + USING (customer_id::text = auth.uid()::text); + +CREATE POLICY payments_select_own ON payments + FOR SELECT + USING (customer_id::text = auth.uid()::text); + +-- ===================================================== +-- Sample Data (for testing) +-- ===================================================== + +-- Insert sample customer (commented out for production) +-- INSERT INTO customers (email, first_name, last_name, company, status) +-- VALUES ('test@example.com', 'Max', 'Mustermann', 'Test GmbH', 'trial'); + +-- ===================================================== +-- Grants +-- ===================================================== + +-- Grant permissions to authenticated users +GRANT SELECT, INSERT, UPDATE ON customers TO authenticated; +GRANT SELECT ON instances TO authenticated; +GRANT SELECT ON subscriptions TO authenticated; +GRANT SELECT ON payments TO authenticated; +GRANT SELECT ON usage_stats TO authenticated; + +-- Grant all permissions to service role (for n8n workflows) +GRANT ALL ON ALL TABLES IN SCHEMA public TO service_role; +GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO service_role; + +-- ===================================================== +-- Comments +-- ===================================================== + +COMMENT ON TABLE customers IS 'Stores customer information and trial status'; +COMMENT ON TABLE instances IS 'Stores LXC instance information for each customer'; +COMMENT ON TABLE emails_sent IS 'Tracks all emails sent to customers'; +COMMENT ON TABLE subscriptions IS 'Stores subscription and payment information'; +COMMENT ON TABLE payments IS 'Stores payment transaction history'; +COMMENT ON TABLE usage_stats IS 'Tracks usage statistics for each instance'; +COMMENT ON TABLE audit_log IS 'Audit trail for important actions'; + +-- ===================================================== +-- End of Schema +-- =====================================================