Skip to content

Exto Console — Project Overview

What is it?

Exto Console is a full-stack SaaS control plane and management platform. It serves as the operational backbone for Exto's multi-tenant workflow product — enabling the Exto team to manage customer instances, provision tenants, handle billing, and giving customers a self-service portal to manage their own workspaces.


Architecture

The system has three main runnable components:

ComponentPathPurpose
API Servercmd/api/REST API backend (Go + Gin)
Background Workercmd/worker/Async job processor
Frontend SPAweb/Admin + customer portal (React + TypeScript)

Data store: PostgreSQL Identity provider: Zitadel (OpenID Connect, user management)


Core Features

Instance Management

  • Register and track infrastructure instances
  • Health monitoring via heartbeat push + scheduled polling
  • Track latency, uptime, CPU/memory/disk metrics
  • Automatic degraded-status detection based on resource thresholds

Customer & Tenant Lifecycle

  • Customer CRUD with plan/billing setup and status (active / suspended / churned)
  • Tenant state machine: requested → provisioning → active → suspended / archived
  • Auto-assignment of tenants to least-loaded instance
  • 30-day grace period before purging churned customers

User Management

Three user types:

  • tenant_users — end-users of the product
  • customer_portal_users — customer admins managing their own org
  • internal_users — Exto team with roles: platform_admin, ops_engineer, finance_admin

Invite flow, Zitadel-backed provisioning, and profile caching with lazy refresh.

Billing & Invoicing

  • Usage event ingestion with idempotent handling
  • Hourly aggregation across meters (active_users, api_calls, storage_gb, workflow_executions, etc.)
  • Plan limits with soft/hard gates
  • Invoice generation with PDF export
  • Dunning workflow: emails at 3/7/14 days overdue → auto-suspend on day 15
  • Credit ledger with atomic operations
  • Per-tenant billing config overrides

Migration Orchestration

  • 5-step pipeline: copy-config → upgrade-config → copy-data → delete-config → delete-data
  • Dry-run enforcement before live execution
  • Snapshot/restore rollback
  • Concurrency limits (max 3 concurrent jobs, no cycles)
  • Structured job logs and verification reports

Auth Configuration (Self-Service)

  • SSO setup (Google/Azure OIDC per org)
  • Password policy, MFA policy, session timeout config
  • Managed through Zitadel Management API

Service Accounts

  • Machine users with client secrets (shown once on creation)
  • Secret rotation, deactivation, and token usage tracking
  • Permission scoping per tenant + instance

Notifications & Webhooks

  • In-app notifications with read/unread state
  • Email dispatch (SendGrid/SMTP)
  • Customer webhook registration with HMAC signing
  • Notification templates with per-channel customization

Insights

  • Tenant-level: DAU/MAU, workflow throughput, error rates
  • Instance-level: health score, p50/p95/p99 latency, uptime

Audit Logging

  • Immutable audit event trail, searchable and exportable

Background Worker Jobs

JobIntervalPurpose
Health Poller5 minPolls instance /internal/health endpoints
Degraded Watcher60 secEvaluates resource thresholds, updates instance status
User Provisioner5 minSyncs users to instances
Tenant Provisioner2 minActivates pending tenant-instance bindings
Usage AggregationHourlyRolls up usage events with watermark tracking
Dunning6 hrSends overdue emails, auto-suspends on day 15
Migration Runner30 secExecutes queued migration jobs
Customer Purge6 hrDeletes churned customers after 30-day grace period

Tech Stack

LayerTechnologies
BackendGo 1.25, Gin, pgx (PostgreSQL driver), goose (migrations), lestrrat-go/jwx (JWT/JWKS)
FrontendReact 19, TypeScript 5.8, Vite 6, Tailwind CSS 4, Zustand, React Query, React Router 7
AuthZitadel (OIDC, Management API, PKCE flow)
EmailSendGrid / SMTP

Portal UIs

Internal Admin Portal

Role-based dashboards for platform_admin, ops_engineer, and finance_admin:

  • Instance health grid and connection management
  • Customer + tenant management with detail views
  • Migration job management
  • Audit log search and export
  • Finance pages (invoices, payments, credits)

Customer Self-Service Portal

  • Tenant viewing and creation
  • Billing/usage dashboard and invoice download
  • Organization settings (billing contact, timezone)
  • Portal user management
  • Auth config (SSO, MFA, password policy)
  • Service account management
  • Webhook registration

Data Model Hierarchy

Customer
  ├── Tenant [1..N]
  │   ├── TenantInstanceBinding → Instance
  │   ├── TenantAuthPolicy
  │   ├── TenantBillingConfig
  │   └── TenantUser [0..N]
  ├── CustomerPortalUser [0..N]
  ├── ServiceAccount [0..N]
  ├── Invoice [0..N]
  ├── Payment [0..N]
  ├── CustomerCredit (ledger)
  └── CustomerWebhook [0..N]

Instance
  └── InstanceConnection (DB/API credentials)

Job (migration)
  ├── JobLog []
  └── JobArtifact []

Project Structure

console/
├── cmd/
│   ├── api/          # API server entry point + dependency wiring
│   └── worker/       # Background worker entry point
├── internal/
│   ├── config/       # Environment config loading
│   ├── db/           # PostgreSQL connection + goose migrations
│   ├── model/        # Data models (instance, customer, user, billing, etc.)
│   ├── handler/      # HTTP request handlers (organized by domain)
│   ├── worker/       # Background job implementations
│   ├── auth/         # JWT middleware + customer scope enforcement
│   ├── zitadel/      # Zitadel Management API client
│   ├── notify/       # Notification dispatcher (email, webhook, in-app)
│   └── audit/        # Audit event logger
├── web/              # React SPA frontend
│   └── src/
│       ├── auth/     # OIDC/PKCE auth flow
│       ├── api/      # Typed API client layer
│       ├── pages/    # Admin, finance, and portal page components
│       └── components/
├── scripts/
│   └── bootstrap/    # Zitadel org/project setup + DB seeding
├── docs/             # Project documentation
├── Makefile
└── .env.example

Configuration

Key environment variables (see .env.example):

env
API_ADDR=:8000
DATABASE_URL=postgres://console:password@localhost:5432/console?sslmode=disable

ZITADEL_ISSUER=http://localhost:8080
ZITADEL_API_URL=http://localhost:8080
ZITADEL_PAT=<personal-access-token>
ZITADEL_ADMIN_ORG_ID=<org-id>
EXTOID_PROJECT_ID=<project-id>
CONSOLE_SERVICE_CLIENT_ID=<id>
CONSOLE_SERVICE_CLIENT_SECRET=<secret>
ZITADEL_WEBHOOK_SECRET=<signing-key-from-bootstrap>

HEALTH_POLL_INTERVAL_SECS=300

EMAIL_PROVIDER=sendgrid
EMAIL_API_KEY=<sendgrid-api-key>
EMAIL_FROM_ADDR=noreply@exto360.com

Running the Project

bash
# Backend
make api        # Start API server on :8000
make worker     # Start background worker

# Frontend
cd web
npm install
npm run dev     # Vite dev server
npm run build   # Production build

API Overview

The REST API is versioned under /api/v1/. Key route groups:

GroupBase PathAuth
Instances/api/v1/instancesJWT (admin roles)
Customers/api/v1/customersJWT (admin or customer-scoped)
Tenants/api/v1/tenantsJWT
Users/api/v1/tenants/:id/usersJWT
Billing/api/v1/customers/:id/...JWT (customer-scoped)
Migrations/api/v1/migrations, /api/v1/jobsJWT
Notifications/api/v1/notificationsJWT
Audit/api/v1/auditJWT (admin)
Server (exto-go)/api/v1/server/instances/:id/...Instance token
Agent/api/v1/agent/instances/:id/...Instance token
Zitadel events/internal/zitadel-eventsHMAC signature