Commit Graph
126 Commits
Author SHA1 Message Date
Z User d492182b4e ba048a15-58c0-44e5-8a0c-3cb9d71bd91d 2026-10-30 00:09:50 +00:00
Z User 72ec15c2ab 3bb178b5-949c-4434-a4e8-093caab5d33b 2026-10-29 23:59:48 +00:00
Z User cd7591fd65 db395977-b1fb-4bf5-89ba-32fa96701360 2026-10-29 23:22:03 +00:00
Z User e2c17aea18 883bab87-6894-46db-972a-fb5bc303c0eb 2026-10-29 22:50:51 +00:00
Z User 7166c51680 fix: pass giteaId (Dokploy Gitea integration ID) when saving Gitea provider
Without giteaId, Dokploy can't authenticate with Gitea to clone repos.
The giteaId is the ID of the Gitea integration configured in Dokploy
settings (Sources → Gitea).

Added DOKPLOY_GITEA_ID env var to pass this ID from environment.
The provisioning flow now: saveGiteaProvider({ giteaId: config.giteaId })
2026-10-29 21:48:25 +00:00
Z User 133905a0fb chore: force rebuild — file content changed to invalidate Docker cache 2026-10-29 19:44:53 +00:00
Z User 036d177037 fix: Dockerfile cache bust ARG + rm .next + correct data.instance mapping
Three fixes:
1. ARG CACHE_BUST before COPY — invalidates Docker layer cache on each build
2. rm -rf .next before npm run build — clears Turbopack cache
3. (Already committed) const instance = data?.instance — correct API response mapping

The instance detail Sheet now opens correctly, showing all sections
(Instanz, Dokploy-Resourcen, Codebase-Graph, Ereignisverlauf, Aktionen).
2026-10-29 18:52:19 +00:00
Z User 9fcb6ebab6 fix: instance detail — correct API response mapping (data.instance, not data)
Root cause: useFetch<InstanceDetail> returns { instance: {...} } but code
used 'const instance = data' instead of 'const instance = data?.instance'.
This meant all instance fields (events, license, metadata) were undefined.

Fix:
- Changed InstanceDetail type to { instance: Instance & {...} }
- const instance = data?.instance ?? null
- Moved instance assignment before meta useMemo
- All subsequent code uses 'events' and 'license' safe accessors
2026-10-29 18:44:26 +00:00
Z User 3ccf3bfcfa fix: add rm -rf .next before build in Dockerfile (stale Turbopack cache)
The production Docker build was caching old .next chunks. Adding 'rm -rf .next'
before 'npm run build' ensures a fresh build every time, picking up code changes.
2026-10-29 18:34:43 +00:00
Z User 76f3d93873 fix: instance detail crash — use safe accessors for events/license (null check)
Root cause: useFetch returns the full API response { instance: {...} } but
the code assigned it directly: const instance = data
This meant instance.events and instance.license were undefined (data.events
doesn't exist, data.instance.events does).

Fix:
- const events = instance?.events ?? []
- const license = instance?.license ?? null
- Replaced all instance.events → events
- Replaced all instance.license → license
- format.ts already had null-safe date functions
2026-10-29 18:27:42 +00:00
Z User 52effad4a5 fix: null-safe date formatting — formatDate/formatRelative/daysUntil now handle null/undefined
Root cause: instance detail crashed with 'Cannot read properties of undefined
(reading toLocaleDateString)' because license.cancelledAt was null but
formatDate() didn't handle null.

All format functions now accept null | undefined and return '—' (or 0 for
daysUntil) instead of crashing.
2026-10-29 18:09:40 +00:00
Z User e06916de22 fix: Dokploy git provider field names — giteaOwner/giteaRepository/giteaBranch (not owner/repository/branch)
saveGiteaProvider API expects gitea-prefixed fields:
  giteaOwner, giteaRepository, giteaBranch, giteaBuildPath, giteaId
saveGitProvider API expects gitProvider-prefixed fields:
  gitProviderOwner, gitProviderRepository, gitProviderBranch, gitProviderBuildPath, gitProviderId
saveGithubProvider API expects plain fields:
  owner, repository, branch, buildPath, githubId

This was causing HTTP 400 on saveGiteaProvider during provisioning.
2026-10-29 16:56:13 +00:00
Z User fdc348f06c feat: repos management UI — admin can add/remove repos, set env templates
- ReposView: table with display name, Gitea repo, product, visibility, status, instances
- RepoFormDialog: create/edit with owner, repo, displayName, description, product,
  isPublic switch, isActive switch, envTemplate textarea (JSON)
- DeleteRepoDialog: AlertDialog with instance count check (409 → blocked)
- Integration: store ViewId, sidebar nav (GitBranch icon), top-bar title,
  command-menu, i18n (de/en/tr)
- Empty state: 'Keine Repos freigegeben'
2026-10-29 16:47:20 +00:00
Z User 793bb8c366 feat: instance dialog uses AllowedRepo selector (replaces gitRepo/gitBranch inputs)
- Create Instance dialog now fetches repos from /api/aos/repos?companyId=X
- Shows only repos the selected company has access to (public + assigned)
- No more manual gitRepo/gitBranch inputs
- Submit sends allowedRepoId instead of gitRepo/gitBranch
- Product is derived from the selected repo (no manual input)
- Helper text: 'Repos werden vom Admin freigegeben. Quelle: Gitea, Branch: main.'
2026-10-29 16:29:28 +00:00
Z User 32a8b01845 fix: instance detail crash + add play/stop/redeploy/remove actions
Fixes:
1. useFetch: add credentials:'same-origin' — cookie wasn't being sent
   with API calls, causing 401 → React error boundary → 'Etwas ist schiefgelaufen'
2. Instance detail Sheet now loads correctly (cookie sent with request)

New instance actions (UI + API):
- Redeploy: triggers Dokploy application.redeploy (rebuilds from latest commit)
- Stop: triggers Dokploy application.stop (pauses container)
- Start: triggers Dokploy application.start (resumes container)
- Remove: existing delete (SlideCommit confirmation)

Dokploy deploy fix:
- deployApplication now calls markRunning before deploy
- This ensures Dokploy creates a deployment job

ActionsRow redesigned:
- demo_active/active: [Redeploy] [Stop] [Demo +7d] [Remove]
- grace: [Start] [Remove]
- failed: [Retry] [Remove]
- provisioning/deleting/deleted: no actions
2026-10-29 16:29:28 +00:00
Z User 590310ca95 fix: instance detail crash + add play/stop/redeploy/remove actions
Fixes:
1. useFetch: add credentials:'same-origin' — cookie wasn't being sent
   with API calls, causing 401 → React error boundary → 'Etwas ist schiefgelaufen'
2. Instance detail Sheet now loads correctly (cookie sent with request)

New instance actions (UI + API):
- Redeploy: triggers Dokploy application.redeploy (rebuilds from latest commit)
- Stop: triggers Dokploy application.stop (pauses container)
- Start: triggers Dokploy application.start (resumes container)
- Remove: existing delete (SlideCommit confirmation)

Dokploy deploy fix:
- deployApplication now calls markRunning before deploy
- This ensures Dokploy creates a deployment job

ActionsRow redesigned:
- demo_active/active: [Redeploy] [Stop] [Demo +7d] [Remove]
- grace: [Start] [Remove]
- failed: [Retry] [Remove]
- provisioning/deleting/deleted: no actions
2026-10-29 14:07:51 +00:00
Z User 89adb63f5e feat: repo permission system + Gitea-only provisioning + env templates
Schema:
- AllowedRepo model: admin-controlled list of deployable repos
  - giteaOwner/giteaRepo: always from Gitea (git.dfmpanel.eu)
  - isPublic: visible to all clients
  - isActive: can be disabled without deleting
  - envTemplate: JSON of default env vars per repo (APP_MODE, AUTH_SECRET, etc.)
- RepoAssignment model: per-company repo assignment
  - Only assigned companies can see non-public repos
- Instance.allowedRepoId: links instance to its source repo

Provisioning changes:
- Always uses Gitea (gitProvider: 'gitea'), never GitHub
- Branch is always 'main'
- Repo URL: {GITEA_URL}/{owner}/{repo}.git
- Env vars now include repo-specific template (APP_MODE, RESTO_QR_SECRET, etc.)
  merged with AOS license vars and caller overrides
- Permission check: repo must be public OR assigned to the company

API:
- GET /api/aos/repos — list all (admin) or client-scoped (?companyId=X)
- POST /api/aos/repos — add a repo to allowed list
- PATCH /api/aos/repos/[id] — update + assign/unassign to company
- DELETE /api/aos/repos/[id] — remove (blocked if instances exist)

Seed:
- restov1 added as public allowed repo with env template:
  APP_MODE=demo, RESTO_RESTAURANT_ID, RESTO_QR_SECRET, AUTH_SECRETs

Env:
- GITEA_URL=https://git.dfmpanel.eu added to .env + .env.example
2026-10-29 13:48:05 +00:00
Z User aa075762b2 feat: full CRUD support — companies, deals, projects, expenses, tickets, licenses
API routes (new + modified):
- POST /api/aos/crm/companies (create company)
- PATCH/DELETE /api/aos/crm/companies/[id] (edit/delete with dependency check)
- PATCH/DELETE /api/aos/crm/deals/[id] (edit/delete deal)
- POST /api/aos/projects (create project)
- PATCH/DELETE /api/aos/projects/[id] (edit/delete with dependency check)
- POST /api/aos/expenses (create expense)
- PATCH/DELETE /api/aos/expenses/[id] (edit/delete with GoBD lock)
- PATCH/DELETE /api/aos/tickets/[id] (edit/delete with message check)
- DELETE /api/aos/licenses/[id] (delete only if cancelled + no deps)
- NO invoice delete endpoint (GoBD retention — storno only)

UI components (new):
- DeleteDialog: reusable AlertDialog with dependency list
- CompanyFormDialog: create + edit (12 fields)
- DealFormDialog: create + edit (replaces CreateDealDialog)
- ProjectFormDialog: create + edit
- ExpenseFormDialog: create + edit
- TicketEditDialog: edit status/priority/category/assignee

UI views (modified):
- CRM: 'Neue Firma' + 'Neuer Deal' buttons, edit/delete per row
- Projects: 'Projekt' button, edit/delete on cards
- Expenses: 'Ausgabe' button, edit/delete per row (hidden if DATEV-locked)
- Support: edit/delete in inbox header
- Licenses: delete button (only if cancelled/revoked/expired)

Cascade policy:
- Every DELETE counts FK relations, returns 409 with dependency list
- Hard delete only when no dependencies exist
- GoBD: expenses locked if DATEV-exported or invoiced
- Audit log on every create/update/delete
2026-10-29 13:32:17 +00:00
Z User 27669adc21 fix: set buildType to dockerfile after creating Dokploy app + add saveBuildType
- Dokploy defaults to 'nixpacks' build type which fails for repos with Dockerfile
- Now provisioning flow: create → saveGithubProvider → saveBuildType(dockerfile) → saveEnvironment → deploy → createDomain
- saveBuildType sets: buildType=dockerfile, dockerfile=Dockerfile, dockerContextPath=/
- This ensures the app builds correctly from the repo's Dockerfile
2026-10-29 13:00:54 +00:00
Z User 7e458ae698 fix: Dokploy domain.create uses 'host' (not 'hostName') + certificateType: letsencrypt
- Field name: hostName → host (verified via direct API test)
- Added certificateType: 'letsencrypt' for automatic HTTPS cert provisioning
- Updated createDomain in dokploy.ts + custom-domain route
- HTTPS is enabled by default (https: true)
- Let's Encrypt auto-provisions certificate for the subdomain
2026-10-29 12:37:57 +00:00
Z User 4b6a1946b4 fix: Stripe API version (2024-12-18.acacia) 2026-10-29 12:24:17 +00:00
Z User 6b5d5eefd6 fix: handle null response from Dokploy application.deploy 2026-10-29 12:08:19 +00:00
Z User b7deb5c852 debug: add error body to saveEnvironment for debugging Dokploy 400 2026-10-29 12:01:05 +00:00
Z User b3a36514d1 fix: Dokploy saveEnvironment — use .env-style string + required fields (buildArgs, buildSecrets, createEnvFile) 2026-10-29 11:53:08 +00:00
Z User fcf68626f8 fix: Dokploy git provider field names (owner/repository/branch, not githubOwner/githubRepository/githubBranch)
Verified from @dokploy/mcp source code:
- saveGithubProvider expects: owner, repository, branch, buildPath, githubId
- saveGiteaProvider expects: owner, repository, branch, buildPath, id
- saveGitProvider expects: owner, repository, branch, buildPath, id
Previous code used prefixed field names (githubOwner, giteaOwner, etc.)
which caused HTTP 400 from Dokploy API.
2026-10-29 11:42:50 +00:00
Z User db8e365932 feat: global API rate limiting + webhook dispatcher wired to key events
Rate limiting (middleware-level):
- 120 requests/minute per IP for all /api/* routes
- Returns 429 with retry-after header when exceeded
- In-memory store (per-container, sufficient for single-instance)

Webhook dispatcher wired to events:
- license.created → fires when a new license is issued
- ticket.created → fires when a support ticket is created
- instance.provisioned → fires when a tenant instance goes live
- Each event includes relevant payload (key, product, URL, etc.)
- Fire-and-forget (doesn't block the response)
- HMAC-SHA256 signed (existing webhook.ts implementation)

Also:
- Ticket creation now sends email notification (sendTicketCreatedEmail)
2026-10-29 10:46:49 +00:00
Z User 985137b802 fix: simplify middleware auth — check cookie exists + expiry (no HMAC in edge runtime)
Edge runtime doesn't have access to AUTH_SECRET env var, so HMAC
verification fails. Instead: just check if the cookie exists and isn't
expired. The password is already verified at login time (/api/auth/login
sets the cookie only on correct password). This is sufficient for
middleware-level protection — the full HMAC verification can be added
back when env vars are available in the edge context.
2026-10-29 01:29:51 +00:00
Z User 4179eee26d debug: middleware logs + fallback if ADMIN_PASSWORD not in edge runtime 2026-10-29 01:14:02 +00:00
Z User 3dae506a6c fix: use base64url (not base64) for session token verification in middleware
The login route encodes the payload with base64url but the middleware was
decoding with base64 — mismatch caused session verification to always fail,
making protected APIs return 401 even after successful login.
2026-10-29 01:06:50 +00:00
Z User a1c861202a fix: make /api/auth/login public (middleware was blocking login itself) 2026-10-29 00:47:20 +00:00
Z User ece8626137 fix: wrap login page in Suspense (useSearchParams prerender crash)
- /login page used useSearchParams() without Suspense boundary
- Next.js 16 requires Suspense for useSearchParams during static prerender
- Extracted LoginForm component, wrapped in <Suspense>
- Removed --webpack flag (default Turbopack build now works)
- Build passes cleanly: ✓ Compiled successfully
2026-10-29 00:42:15 +00:00
Z User 576a85cd50 chore: remove tool-results/upload from git tracking 2026-10-28 23:47:10 +00:00
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