Security, compliance and operations
Built to hold many clients' customer data
Loyalti is multi-tenant from the first line: every table carries the tenant, every query is scoped, and the platform runs on a stack that your operations team already knows.
Tenant isolation
Tenant id on every business table and row-level security enabled on 37 of them. Users, till keys, webhooks and API tokens belong to one tenant; cross-tenant access returns 403 and is covered by tests. A super-admin panel manages tenants without touching their data.
Authentication and access
JWT with short-lived access and refresh tokens in secure cookies, password hashing with the current Symfony hasher, two-factor sign-in for platform administrators, forced password change for generated accounts, login throttling. Roles per user and access limited to chosen stores for managers.
Secrets and keys
Provider credentials for SMTP, Twilio, WhatsApp, Telegram, Viber, push and S3 are stored encrypted in the database with a key that lives only in the environment. Till keys are hashed; the plaintext is shown once. Webhooks sign their payloads.
Audit and traceability
Every change in the back office writes an audit entry with the actor, the entity, the action and the payload, partitioned by month. Point movements are an append-only ledger; orders keep the rules that produced their points.
Privacy and consent
Consent texts with versions per tenant, a record of who accepted which version and from which source, channel opt-outs honoured by campaigns, and a privacy jurisdiction setting per tenant for GDPR-style processing. Member data export and deletion are API operations.
Backups and recovery
Scheduled database dumps with retention locally and in S3-compatible storage, restore from the back office, a schema that installs from two migrations and is verified identical to the migrated history before each release.
Deployment
Docker images, Swarm stack, one deploy to a live server
Two images, backend and nginx with the built front end, published by the CI pipeline. A Swarm stack with PostgreSQL and TimescaleDB, PgBouncer, Redis, RabbitMQ, fifteen worker services, a scheduler and a backup service. Traefik terminates TLS. The first deploy bootstraps the swarm, generates secrets, runs migrations, imports cities and translations and checks the health endpoint.
The same stack runs on a laptop with Docker Compose for development, with Mailpit for e-mail and the monitoring stack on request.
| Component | Version |
|---|---|
| PHP | 8.5.12 with Symfony 7.4, DBAL without ORM |
| PostgreSQL | 16 with TimescaleDB, PgBouncer in front |
| Queues and cache | RabbitMQ 4, Redis 7 |
| Front end | Nuxt 3.21, Vue 3.5, TypeScript, Tailwind |
| Monitoring | Prometheus, Grafana, Loki, Alertmanager |
| Tests | 3 155 PHPUnit tests, PHPStan at level max, Playwright end-to-end |
Operations in the product
Queues, logs and alerts in the super-admin panel
Queue depths, failed messages with a retry, application logs, alert rules, backup runs and a health page sit in the panel, so a platform operator rarely needs a shell. Prometheus metrics and the health endpoint plug into whatever your team already watches.
