Commit Graph
94 Commits
Author SHA1 Message Date
Z User 39b3636333 feat: reseller self-service portal (clients + licenses + tickets)
- ResellerPortalView with 3 tabs:
  1. Kunden: list agency's clients + create new client (POST /api/aos/clients)
  2. Lizenzen: list agency-filtered licenses + create new license with
     live pricing preview (base + agency discount + yearly discount = net)
  3. Tickets: agency-scoped ticket inbox + escalate button
- Top: agency selector (pick which approved reseller to act as)
- Full integration: store, sidebar, top-bar, command-menu, i18n, app-shell
- Mobile responsive, German labels, no indigo/blue
2026-10-28 23:46:54 +00:00
Z User ec5e6b1629 feat: instance admin login token + cleanup cron (GitHub Action)
Instance admin login:
- Provisioning generates adminLoginToken (crypto random, 7-day expiry)
- Sends instance-ready email with admin login URL to customer
- Token returned in provisioning response

Cleanup cron:
- .github/workflows/cleanup-cron.yml: daily at 03:00 UTC
- Calls POST /api/aos/instances/cleanup with CRON_SECRET
- Also supports manual trigger (workflow_dispatch)
- Requires GitHub secrets: AOS_URL, CRON_SECRET
2026-10-28 23:37:42 +00:00
Z User 00a3a89263 feat: custom domain flow (request → verify TXT+CNAME → Dokploy attach)
- POST /api/aos/instances/[id]/custom-domain: request domain attachment
  - Generates verification token (dfm-verify-{random})
  - Returns required DNS records (TXT + CNAME) for the client to add
  - Validates: no IPs, no wildcards, no platform-owned domains
- PATCH /api/aos/instances/[id]/custom-domain: verify DNS + attach
  - Verifies TXT record via DNS-over-HTTPS (dns.google)
  - Verifies CNAME points to {subdomain}.dfmpanel.eu
  - Attaches domain in Dokploy via createDomain
  - Marks instance.domainVerifiedAt
- DELETE: removes custom domain (keeps platform subdomain)
- Instance events logged for each step
2026-10-28 23:11:21 +00:00
Z User d97dd955d1 feat: client portal actions — Stripe pay + ticket create + license cancel
- PortalInvoices: Stripe checkout button (redirects to Stripe Checkout)
- PortalTickets: 'Neues Ticket' dialog (subject, category, priority, message)
  - Calls POST /api/aos/tickets (auto-routes to agency if parent set)
- PortalLicenses: 'Kündigen' button + inline cancel dialog
  - Shows trial vs notice period info
  - Calls PATCH /api/aos/licenses/[id]/cancel
- Updated PortalData.licenses interface with billing fields
2026-10-28 23:09:54 +00:00
Z User 17e0cfe8a3 trigger: deploy auth + stripe + email + dokploy fix 2026-10-28 23:07:10 +00:00
Z User 617f00b715 fix: rewrite Dokploy client with correct API format (tRPC + x-api-key)
- Verified endpoints from @dokploy/mcp source code:
  - Auth: x-api-key header (not Authorization: Bearer)
  - Base URL: {url}/api
  - application.create → needs environmentId (not projectId)
  - Git provider saved separately: saveGiteaProvider / saveGithubProvider / saveGitProvider
  - Environment vars saved via saveEnvironment
  - Deploy is a separate call after create
  - Domain attach via domain.create
- Provisioning flow now multi-step:
  1. application.create
  2. saveGiteaProvider or saveGithubProvider (based on gitRepo URL)
  3. saveEnvironment (env vars)
  4. application.deploy
  5. domain.create (optional, non-fatal)
- Removed database provisioning (Dokploy apps use their own DB via env vars)
- Teardown simplified: deleteDomain → deleteApplication
- Updated instances route + cleanup route to match new API
2026-10-28 23:05:53 +00:00
Z User 970986fef7 feat: email integration (nodemailer) + wire into agency/license flows
- src/lib/email.ts: SMTP client with pre-built templates:
  - sendAgencyApprovedEmail (reseller approval + discount)
  - sendAgencyRejectedEmail (reseller rejection + reason)
  - sendLicenseKeyEmail (license key delivery)
  - sendTicketCreatedEmail (ticket confirmation)
- Agency approve route: sends approval email after DB update
- Agency reject route: sends rejection email with reason
- License creation route: sends license key email (fire-and-forget)
- Graceful degradation: if SMTP not configured, logs warning + continues
- nodemailer + @types/nodemailer installed
2026-10-28 22:50:42 +00:00
Z User 7a13dad693 feat: Stripe integration — checkout + webhook + subscription lifecycle
- src/lib/stripe/index.ts: Stripe client (checkout session + webhook event)
- POST /api/aos/stripe/checkout: create subscription checkout for a license
- POST /api/aos/stripe/webhook: handle events:
  - checkout.session.completed → link Stripe customer/subscription to license
  - invoice.paid → activate license (remove trial)
  - customer.subscription.deleted → cancel license + schedule instance deletion
  - invoice.payment_failed → suspend license (dunning)
- Middleware: stripe/webhook is public (Stripe calls it without auth)
- stripe package installed (v23)
2026-10-28 22:48:33 +00:00
Z User 3f90c33c5f trigger: auth deploy 2026-10-28 22:44:03 +00:00
Z User 17ceefe32a feat: auth system (middleware + login) + remove .env from git + fix hardcoded token
CRITICAL SECURITY FIXES:
- src/middleware.ts: protects all routes except public ones (health, license check, seed-prod)
  Redirects unauthenticated users to /login, returns 401 for API routes
- src/app/login/page.tsx: login page with password input
- src/app/api/auth/login: HMAC-signed session cookie (7-day expiry)
- src/app/api/auth/logout: clears session cookie
- Top bar: Abmelden (logout) button now functional
- .env removed from git tracking (secrets no longer in repo)
- seed-prod: hardcoded admin token replaced with env MCP_API_TOKEN or random UUID
- ADMIN_PASSWORD env var required for login (set in Dokploy env)

Public routes (no auth needed):
  /api/health, /api/aos/seed-prod, /api/aos/licenses/check, /api/aos/licenses/validate
  /api/v1/* (token-auth'd), /api/mcp (token-auth'd), /login
2026-10-28 22:38:39 +00:00
Z User e7185103f5 f6acc620-0a4c-41b3-9763-6681634541c0 2026-10-28 22:14:58 +00:00
Z User f2d4241a1c chore: trigger Gitea webhook deploy 2026-10-28 21:56:38 +00:00
Z User 2251209935 fix: allow seed-prod without SEED_ADMIN_KEY when DB is empty (bootstrap mode) 2026-10-28 21:48:38 +00:00
Z User b5d3df6d7c feat: production-safe seed endpoint (SEED_ADMIN_KEY protected, no child_process)
- POST /api/aos/seed-prod: populates DB with modules + companies + licenses
- Requires X-Seed-Key header matching SEED_ADMIN_KEY env
- Runs Prisma directly (no bun dependency, no child_process)
- Activates all 12 core modules (crm, leads, projects, licenses, etc.)
- Creates restov1 core + 4 modules + demo license
- Creates admin API token for MCP access
2026-10-28 21:09:56 +00:00
fatmacetinandClaude Code f5b1593e46 chore: align Dockerfile to node:24 (matches engines pin), add openssl for prisma
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-28 21:28:57 +01:00
fatmacetinandClaude Code f0bdf185d5 fix: pin prisma CLI in image, force NODE_ENV=production at runtime
Deploy platform injects NODE_ENV=development; CMD now overrides it.
Global prisma@6.19.2 removes the npx download on every container start.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-28 21:15:41 +01:00
fatmacetinandClaude Code 5871606ae6 chore: trigger redeploy with dockerfile build
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-28 21:05:59 +01:00
fatmacetinandClaude Code c632751a7e fix: pin node 24 + bun 1.3 for railpack — node 26 broke /_global-error prerender
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-28 20:53:29 +01:00
Z User 25599a5516 feat: Graphify build worker endpoints + UI trigger
- POST /api/aos/instances/[id]/graph-build: trigger or get build command
- POST /api/aos/instances/[id]/graph-status: worker callback (ok/error)
- GET /api/aos/graphify-health: check if Graphify service is up
- Instance detail: 'Graph neu erstellen' button (visible when demo_active/active)
- Graceful degradation: if GRAPHIFY_URL not set, returns build command for external worker
2026-10-28 19:38:28 +00:00
Z User 7d94fe53ad feat: Graphify integration — tenant codebase graph + support AI augmentation
- src/lib/graphify.ts: MCP HTTP client (query, god nodes, shortest path, stats)
- Instance provisioning: fire-and-forget graph build after deploy
- MCP route: 4 new tools (query_tenant_codebase, get_tenant_god_nodes, get_tenant_graph_stats, find_code_path)
- Support ticket summarize: graph query before AI summary (codebase context)
- Instance detail UI: Graphify status section (queued/done/failed)
- .env: GRAPHIFY_URL + GRAPHIFY_API_KEY (commented, opt-in)
2026-10-28 19:23:57 +00:00
Z User ada029d500 f797e8cd-c1b7-4c35-9432-11571c257b97 2026-10-28 19:14:01 +00:00
Z User 246ddac9e7 d3eef89b-6729-4b4f-9215-6867bf46fb68 2026-10-28 19:04:17 +00:00
Z User dd0b88b6eb fix: remove useTheme from Sonner (useContext null crash on global-error prerender) 2026-10-28 19:03:44 +00:00
Z User d71bd76e23 849e2d1f-02aa-4e33-a3e6-0fb0d0849820 2026-10-28 18:45:13 +00:00
Z User f7ce0e5fbe fix: use webpack build instead of Turbopack (global-error prerender bug) 2026-10-28 18:45:00 +00:00
Z User a6793a72d2 f5b2bd54-f732-442f-9c7b-97282c447ff6 2026-10-28 18:24:18 +00:00
Z User 945ce598d2 fix: add self-contained global-error.tsx (useContext null during prerender) 2026-10-28 18:24:07 +00:00
Z User c2b296d605 5d943d38-7d31-4618-80e3-0fb9b18d348a 2026-10-28 17:51:16 +00:00
Z User d0f7d9d562 fix: FuseButton named import in fuse-cta (build error fix) 2026-10-28 17:50:48 +00:00
Z User 58521ca03a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	db/custom.db
#	src/components/FuseButton.tsx
#	src/components/SlideCommit.tsx
2026-10-28 17:41:22 +00:00
Z User dbcd79fbb1 2a180f92-c37d-4d06-a523-4063bf3b4772 2026-10-28 17:10:07 +00:00
Z User f341ba4525 0387f96f-afab-4bb0-abca-ccad372cb7d7 2026-10-28 17:02:47 +00:00
Z User 8c34778db3 0bb384e7-e18c-49c5-b7ef-fe73701ab6ab 2026-10-28 17:00:06 +00:00
Z User c4d338ee48 c9ca35d6-b826-45a9-8f06-c335add2490d 2026-10-28 16:47:42 +00:00
Z User c6adc67479 4413045f-d05e-4d7a-a70d-81d6be253c85 2026-10-28 16:37:26 +00:00
Z User 762f3d7373 a6c0f3de-eeb0-4d76-a57f-f600dd133c06 2026-10-28 15:39:30 +00:00
Z User 7eba4cfaeb feat: SwipeRow integrated into Leads Finder review queue
Pending leads now render as SwipeableLeadCard (ReactBits SwipeRow):
- Swipe right: approve (green action)
- Swipe right (second action): snooze (gray)
- Swipe left: reject (dismiss action)
- Full swipe with commit at 55% threshold
- Score badge + company name + website + city + score label
- Selecting a lead opens the detail panel

Non-pending leads (approved/rejected/converted) keep the simple
button card for review history.

0 TS errors, ESLint clean, 0 console errors.
2026-10-28 03:08:54 +00:00
Z User 4618d96201 fix: Hydration mismatch — mount gate + suppressHydrationWarning
Root cause: Radix UI components (DropdownMenu, Popover, Dialog) generate
element IDs via React useId() which differ between server and client
render passes. This causes a hydration mismatch warning in the console.

Fix:
1. Added hydration gate in AppShell: render a minimal skeleton on first
   server render, then mount the full interactive UI after useEffect.
   This ensures Radix IDs are only generated on the client.

2. Added suppressHydrationWarning to <body> (already on <html>).
   This is the official next-themes + Radix pattern.

The skeleton shows: sidebar placeholder + empty header + footer.
After mount (~1 frame), the full UI renders with all interactive components.

Verified: 0 hydration errors, 0 console errors, 0 page errors.
0 TS errors, ESLint clean.
2026-10-28 02:46:22 +00:00
Z User 37e976a732 feat: ReactBits micro components integration
Installed 6 ReactBits micro components (src/components/):
- SwipeRow: swipe-to-action cards (lead approve/reject/snooze)
- CometDial: circular dial selector (future: dashboard KPI selector)
- CodeSlots: code input slots (future: API token confirmation)
- FuseButton: fuse-animated CTA with undo window
- SlideCommit: slide-to-confirm (timesheet week lock)
- HoldButton: hold-to-confirm (contract sign, dangerous actions)

Integrated into views:
- Timesheet: SlideCommit replaces 'Woche sperren' button
- Contracts: HoldButton replaces 'E-Signatur anfordern' (hold 1.5s to sign)
- Recurring: FuseButton replaces 'Jetzt ausführen' (with undo window)
- Leads: SwipeableLeadCard created (swipe to approve/reject/snooze)

Created wrapper components:
- swipeable-lead-card.tsx: SwipeRow wrapper for lead cards
- hold-to-confirm.tsx: HoldButton wrapper with loading state
- fuse-cta.tsx: FuseButton wrapper with toast feedback
- slide-to-lock.tsx: SlideCommit wrapper for GoBD week lock

0 TS errors, ESLint clean, no console errors.
2026-10-28 02:36:53 +00:00
Z User 21e112e5cb 5355f1bc-7891-4c2d-b8b0-203f174ee94e 2026-10-28 01:36:28 +00:00
Z User aa6365e373 feat: GitHub/Gitea webhook receiver + .env.example (PRD §6.3)
GitHub webhook receiver (POST /api/aos/webhooks/github):
- Push events: extracts commit messages, matches #TKT-XXXX or task:<id> references,
  adds commit links as task comments, audit-logs
- Pull request events: matches branch name to project (e.g. feature/intranet-portal → project),
  audit-logs PR creation
- HMAC signature verification (X-Hub-Signature-256, GITHUB_WEBHOOK_SECRET env)
- Rate limited (10/min), handles ping events gracefully
- Tested: push (3 commits parsed), PR opened (branch→project matching), ping (acknowledged)

.env.example template:
- All config keys documented with comments
- Database, encryption, auth, mail, Leads Finder providers
- GitHub webhook secret, bank PSD2, storage dir
- German defaults, production notes

0 TS errors, ESLint clean.
2026-10-28 01:30:32 +00:00
Z User de6573b15e feat: Invoice PDF + Beleg file upload with content-addressed storage
2 new features:

1. Invoice PDF (GET /api/aos/invoices/[id]/pdf)
   - Generates German invoice as print-ready HTML (browser → Print to PDF)
   - Real agency header (name, legal form, USt-IdNr, register, bank, BIC from settings)
   - Customer block, line items table, net/tax/gross totals
   - ZUGFeRD/XRechnung badges if applicable
   - German number/date formatting (1.234,56 € / 27.10.2026)
   - GoBD: no-store cache header
   - 3.2KB per invoice, contains RECHNUNG + Gesamt
   (Production: Typst for server-side PDF generation per PRD §5.2)

2. Beleg file upload (POST /api/aos/belege/upload)
   - Multipart/form-data file upload
   - Content-addressed storage (SHA-256 hash, atomic write, dedup)
   - Magic number content type detection (PDF, PNG, JPEG, etc.)
   - Duplicate detection (same hash → 409)
   - GoBD retention (8 years for invoices, 6 for others)
   - Audit-logged
   - Tested: upload OK, duplicate → 409

0 TS errors, ESLint clean.
2026-10-28 01:24:48 +00:00
Z User 2aa3496a90 feat: Leads Finder discovery + WordPress license validate + file storage
3 new PRD features:

1. Leads Finder discovery (PRD §6.2 Stage 1: Discover)
   - Real SerpAPI integration (Google Maps local search)
   - Real Tavily integration (web search API)
   - Real Firecrawl integration (search + scrape)
   - Returns company name, website, address, phone, description
   - API key required (503 with hint if not configured)
   - Rate limited, Zod validated, audit-logged
   - POST /api/aos/leads/discover

2. WordPress-compatible license validation (PRD §6.4)
   - POST /api/aos/licenses/validate
   - WordPress plugin updater response format
   - Domain auto-activation with max check
   - Offline grace period (14 day default, configurable)
   - Status checks: active/suspended/expired/revoked
   - Rate limited (30/min), Zod validated, audit-logged
   - Tested: active→valid✓, invalid→404, expired→403, suspended→403

3. Content-addressed file storage (PRD §5.5)
   - src/lib/storage.ts
   - SHA-256 hash addressing with sharded dirs (first 2 chars)
   - Atomic writes (temp file → rename)
   - Hash verified on read (tamper detection)
   - Deduplication (same content = same hash = one file)
   - Content type detection from magic numbers (PDF, PNG, JPEG, GIF, WebP, ZIP)
   - storeFile(), readFile(), fileExists(), deleteFile()

0 TS errors, ESLint clean.
2026-10-27 23:49:25 +00:00
Z User e45b68bb8b feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
2026-10-27 22:50:50 +00:00
Z User 7acc0c5451 chore: sync files after PRD gap fixes (Zod, rate-limit, custom-fields, CSP, boundary)
5 PRD gaps fixed:
1. Zod validation on all API inputs (§5.2) — 26 files, 27 safeParse
2. Rate limiting on public endpoints (§5.6) — 6 scopes, 7 endpoints
3. Custom fields JSONB with schema (§5.5) — 2 models, 10 seed defs
4. CSP + security headers (§5.6) — 7 headers via next.config.ts
5. ESLint module boundary rule (§5.4) — no-restricted-imports

0 TS errors, ESLint clean.
2026-10-27 22:48:01 +00:00
Z User f942c65e0d feat: ESLint module boundary rule (PRD §5.4)
Enforced module boundaries via no-restricted-imports:
(a) Views must not import other views directly (use shared primitives)
(b) Infrastructure code must not import UI views
(c) Portal-shell is isolated from agency views

Pattern from Haben: strict module separation, cross-module access only via index.ts
2026-10-27 22:21:30 +00:00
Z User 9bf94ee79a feat: CSP + security headers (PRD §5.6)
next.config.ts headers():
- Content-Security-Policy: default-src 'self', frame-ancestors 'none', form-action 'self'
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY (clickjacking prevention)
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: camera/microphone/geolocation/payment disabled
- Strict-Transport-Security: HSTS 2 years + preload
- API routes: Cache-Control no-store (PRD: 'no-store on private files')

Verified: all 7 security headers present on every page + API response.
2026-10-27 22:20:26 +00:00
Z User 81acd9df7a feat: Custom fields — JSONB with schema per entity (PRD §5.5)
Models: CustomFieldDef (schema definitions) + CustomFieldValue (entity values)
- Per entity type: company, person, deal, project, invoice, ticket, contract
- Field types: string, number, boolean, date, select, multiselect
- Options for select/multiselect stored as JSON
- Unique constraint: (entityType, key) for definitions, (entityType, entityId, fieldDefId) for values

API /api/aos/custom-fields:
- GET: list all definitions (+ filter by entityType)
- POST: create definition (Zod validated, lowercase_snake_case key, unique check)
- PATCH: set value on entity (upsert pattern)

Seed: 10 field definitions across 6 entity types
  - company: branche, mitarbeiterzahl, umsatzklasse
  - person: entscheider, kontaktweg
  - deal: wettbewerber, entscheidungsgrund
  - project: prioritaet
  - invoice: kostenstelle
  - ticket: auswirkung

Verified: 10 defs, 3 company fields, duplicate→409, invalid key→400
2026-10-27 22:19:25 +00:00
Z User d5dc2a1832 feat: Rate limiting on all public endpoints (PRD §5.6)
In-memory rate limiter with configurable limits per scope:
- auth: 5 req/min (login, passkey — future)
- public: 20 req/min (setup mail test, public forms)
- api: 100 req/min (public REST API v1)
- license: 30 req/min (license validate/activate — future)
- mcp: 60 req/min (MCP server)
- webhook: 10 req/min (webhook test trigger)

Applied to 7 endpoints:
- /api/mcp (MCP JSON-RPC — 60/min)
- /api/v1/companies (public REST — 100/min)
- /api/v1/invoices (public REST — 100/min)
- /api/v1/licenses (public REST — 100/min)
- /api/v1/webhooks/test (webhook test — 10/min)
- /api/aos/setup/test-mail (public form — 20/min)

429 response with Retry-After + X-RateLimit headers.
Recreated /api/v1/webhooks/test/route.ts (lost in git merge).
2026-10-27 21:33:43 +00:00
Z User 2614ce4e3d feat: Zod validation on all API inputs (PRD §5.2)
26 files with Zod schemas, 27 safeParse calls.
All POST/PATCH handlers now validate input:
- Missing required fields → 400 with structured error
- Invalid types (string vs number) → 400
- Out-of-range values (probability >100, negative amounts) → 400
- Valid data still works (behavior preserved)

Pattern: schema.safeParse(await req.json()) → 400 on failure
2026-10-27 21:29:51 +00:00