Skip to main content

Admin Service

The Admin Service is the platform control plane for Vecton — tenant lifecycle, billing, deployments, monitoring, backups, and configuration. It does not host tenant business data; the tenant services (CRM, webshop, warehouse, etc.) own that.

Architecture

            ┌────────────────────────────────────┐
│ Admin Frontend (Vue 3 + Vuetify) │
│ https://admin.vecton.hu │
└─────────────────┬───────────────────┘


┌────────────────────────────────────┐
│ Admin Backend (Laravel 12) │
│ /api/admin/* │
└────┬───────┬──────┬──────┬──────┬───┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐
│ PG │ │Redis│ │RabM│ │ S3 │ │OTel│
└────┘ └────┘ └────┘ └────┘ └────┘

┌──────────────────┐
│ Identity API │ ← OAuth, users, abilities
└──────────────────┘
┌──────────────────┐
│ DevOps API │ ← K8s/ArgoCD/Harbor orchestration
└──────────────────┘
┌──────────────────┐
│ Tenant services │ ← CRM/webshop/warehouse proxy
└──────────────────┘

Tech Stack

ComponentTechnology
BackendLaravel 12 + PHP 8.4
ServerFrankenPHP (Octane)
FrontendVue 3.5 + TypeScript + Vuetify 3 + Pinia
DatabasePostgreSQL 16
Cache/SessionsRedis 7
QueueRabbitMQ via php-amqplib
Object StorageRook-Ceph S3 (private + public buckets)
OpenAPIDedoc Scramble auto-generation
Frontend SDK@hey-api/openapi-ts generated typed client
TracingOpenTelemetry → OTLP → Tempo/Loki/Grafana
AuthOAuth 2.0 token issued by Identity service

Source layout

Backend (main/main-backend/)

PathPurpose
app/Http/Controllers/Tenant/Tenant lifecycle, scaling, versions, storage, email, SMS, health (split from a former 1076-line monolith)
app/Http/Controllers/Admin/LogViewer, Impersonation, SLA, UsageMetering, TenantActivity, TenantEmail (snapshot-based)
app/Http/Controllers/ (root)Auth, Dashboard, Billing, Feature, Backup, Monitoring, Infrastructure, Registry, Deployment, ArgoCD, DatabaseAccess, Maintenance, Notification, Contact, Users, AuditLog, ServiceDefaultVersion, VersionTracking, PlatformService, Status
app/Services/DevOpsApiService, IdentityService, IdentityAdminService, TenantSystemApiService, AuditService, FeatureService, ScalingConfigService, BackupConfigService, EventPublisher, RabbitMQService, AlertDispatchService, EndpointCheckerService, NotificationService, TenantProvisioningService, VersionTrackingService
app/Providers/AppServiceProvider.phpRegisters admin.tenants.email.* Gate abilities
app/Http/Middleware/VerifyOAuthToken.phpValidates Bearer tokens against Identity (60s cache)
routes/api.phpAll admin routes; mounted under /api/admin/
config/scramble.phpOpenAPI scope configuration (api_path = api/admin)

Frontend (main/main-frontend/)

PathPurpose
src/lib/axios.tsSingle shared axios instance with auth/refresh interceptor
src/api/index.tsBridges the generated SDK to the shared axios
src/api/generated/Auto-generated typed SDK from swagger/admin.json
src/types/api.tsHand-curated domain types (Tenant, EmailAccount, ScalingConfig…)
src/stores/Pinia stores (one per domain area)
src/pages/File-based routes (unplugin-vue-router)
src/views/tenants/Tenant detail per-tab components
src/navigation/vertical/index.tsSidebar grouping: Platform / Configuration / Administration

Authentication & Authorization

  1. OAuth flow: the frontend redirects to Identity (/oauth/authorize), gets a code, the backend exchanges it for a Bearer token (AuthController::callback).
  2. Per-request validation: every /api/admin/* request hits VerifyOAuthToken, which calls Identity::getUserFromToken and caches the user 60 s by token hash.
  3. Authorization layers:
    • is_admin: true from Identity → access granted to every admin endpoint.
    • Granular abilities defined in AppServiceProvider::ADMIN_ABILITIES and checked via can:admin.tenants.email.view route middleware.
    • User-permission entries support glob wildcards: admin.tenants.email.* matches admin.tenants.email.view.
  4. Audit log: mutating endpoints call AuditService::log() after success; entries persist in audit_logs (indexed on target, user_email, action, created_at).

Design notes:

  • The OAuth ?token= query-string fallback was removed (audit #03).
  • Production exception responses no longer leak getMessage() details unless APP_DEBUG=true or the throwable is an HTTP exception (audit #06).
  • IpWhitelist, Subscription mutations, and Backup::restoreToNew are now wrapped in transactions (audit #11, #15).

Functional areas

AreaRoutesNotes
Tenants/api/admin/tenants/*CRUD, suspend/activate, K8s status, services list
Provisioning/api/admin/tenants/{id}/{provisioning-status,retry-provisioning,sync-config}Async job + log stream
Versions/api/admin/tenants/{id}/versionsImage tag updates per service
Scaling/api/admin/tenants/{id}/scaling-configPer-service scale-to-zero, replicas, CPU/mem
Storage/api/admin/tenants/{id}/storage-usageBucket quotas, usage
Health/api/admin/tenants/{id}/service-healthPer-tenant service health probes
Email (CRM proxy)/api/admin/tenants/{id}/email/*Accounts, assignments, usage, bonus, provision
Email (snapshot admin)/api/admin/tenants/{id}/email/{overview,messages,quarantine,…}Platform-wide diagnostics, gated by admin.tenants.email.* abilities
SMS/api/admin/tenants/{id}/sms/*Twilio/SeeMe accounts
Billing/api/admin/{plans,subscriptions,payments}Plan CRUD, subscription state, payment recording
Features/api/admin/{feature-groups,feature-definitions}/*Feature flag/limit catalog and per-tenant overrides
Monitoring/api/admin/monitoring/{dashboard,endpoints,alert-channels,metrics}Endpoint health + Prometheus metrics
Backups/api/admin/backups/*List, trigger, restore, 3-tier config (defaults / plan / tenant)
Database access/api/admin/database-access/{ip-whitelist,platform/users,tenants/{id}/users}Postgres user lifecycle + IP whitelist
Maintenance/api/admin/maintenance-windowsScheduled platform downtime announcements
Audit logs/api/admin/audit-logsSearchable mutation history
Service versions (defaults)/api/admin/service-default-versionsDefault image tags for new tenants
Version tracking/api/admin/versions/*Per-tenant version drift dashboard
Infrastructure/api/admin/infrastructure/{overview,nodes,postgresql,rabbitmq,ceph}Cluster overview
Registry/api/admin/registry/*Harbor projects/repos/tags
ArgoCD/api/admin/argocd/*Read-only application list
Notifications/api/admin/notifications/*Admin-targeted notification feed (IDOR-scoped)
Impersonation/api/admin/{users/{id}/impersonate,impersonation/{stop,history}}Impersonate a tenant user
Platform services/api/admin/platform-servicesImage / PDF / SMTP / Scan health (parallel Http::pool)
Tenant logs/api/admin/tenants/{id}/logsLoki query proxy with service whitelist

OpenAPI workflow

The backend generates an OpenAPI 3.0 spec from PHPDoc + route inspection via Scramble:

# main-backend
php artisan scramble:export # writes docs/api.json
cp docs/api.json ../main-frontend/swagger/admin.json

The frontend regenerates a typed Axios SDK:

# main-frontend
npm run api:export # curls the running backend's /docs/api.json
npm run api:generate # runs @hey-api/openapi-ts → src/api/generated/

Use the SDK in stores via:

import { sdk } from '@/api'

const { data } = await sdk.tenantsIndex({ query: { perPage: 10 } })

The generated client shares src/lib/axios.ts, so authentication, refresh, and 422 toast handling apply uniformly.

To enrich the response types, annotate Laravel controllers with PHPDoc @response blocks; Scramble picks them up on the next export.

Configuration

Key environment variables (see main-backend/.env.example for the full list):

VariablePurposeExample
APP_URLBackend URLhttps://admin.vecton.hu
APP_DEBUGDetailed exception messages — never true in productionfalse
IDENTITY_URLIdentity service base URLhttp://localhost:8000
OAUTH_CLIENT_ID / OAUTH_CLIENT_SECRETOAuth client credentials issued by Identity
DEVOPS_API_URL / DEVOPS_API_TOKENDevOps orchestration API
TENANT_SYSTEM_API_TOKENToken for proxying calls to tenant services
TENANT_{WEBSHOP,WAREHOUSE,CRM,ANALYTICS,WEBHOOK}_URLLocal-dev fallback URLshttp://localhost:801{0..5}
RABBITMQ_{HOST,PORT,USER,PASSWORD,VHOST}RabbitMQ connection
LOKI_URLLoki endpoint for tenant log viewerhttp://grafana-loki.monitoring.svc.cluster.local:3100
OTEL_EXPORTER_OTLP_ENDPOINTOTLP collectorhttp://otel-collector:4318

Development

# From vecton/main/main-backend
composer install
php artisan migrate --seed
php artisan serve --port=8002 # or `composer dev` for full stack

# From vecton/main/main-frontend
npm install
npm run dev # starts Vite on :5174

Useful npm scripts:

npm run typecheck       # vue-tsc --noEmit
npm run lint # eslint --fix
npm run api:export # refresh swagger/admin.json from running backend
npm run api:generate # regenerate typed SDK

Useful artisan commands:

php artisan scramble:export              # regenerate OpenAPI spec
php artisan migrate --seed
php artisan test # PHPUnit feature + unit tests

Testing

The backend has 350+ tests covering controllers, services, and middleware. Notable:

  • tests/Feature/Middleware/VerifyOAuthTokenTest.php — OAuth header parsing, identity rejection, query-token-rejection.
  • tests/Feature/Authorization/AdminGatesTest.phpadmin.tenants.email.* ability resolution including wildcards.
  • tests/Unit/Models/TenantNamespaceTest.php — Tenant name → namespace mutator.
  • tests/Feature/NotificationControllerTest.php — IDOR scope.
  • tests/Feature/Tenant/ScalingConfigTest.php — Scaling-config response shape.

Run the suite:

php artisan test

The base Tests\TestCase bypasses VerifyOAuthToken for most tests; the middleware itself is exercised by VerifyOAuthTokenTest which re-enables it explicitly.

Production checklist

Before deploying a new release:

  • APP_DEBUG=false in the production env
  • All five TENANT_*_URL envs and DEVOPS_API_URL resolve from inside the cluster
  • Migrations applied (php artisan migrate --force)
  • OpenAPI spec regenerated and frontend rebuilt
  • .env.production contains the right VITE_OAUTH_CLIENT_ID and VITE_IDENTITY_URL
  • OTel collector is reachable; otherwise set OTEL_PHP_AUTOLOAD_ENABLED=false
  • Audit logs index migration applied
  • Feature definitions seeded for the new release's infra.* slugs