Django loyalty-card SaaS whose first tenant is Marta Banaszek Atelier-Café. The production service is available at club.mbstudio.online.
This repository began as a source-only recovery from /var/www/loyalty_platform
and now contains the modular SaaS conversion completed through Phase 11. Local
uncommitted work is not deployed; a pushed main release deploys only after the
configured SQLite/build/audit and MariaDB CI gates pass.
Production secrets, customer data, generated cards, Wallet passes, virtual
environments, databases, logs, and nested Git metadata are intentionally
excluded.
| Area | Current state |
|---|---|
| Technical conversion | Phases 0–11 complete; modular Django platform and operational safety controls implemented |
| Automated baseline | 240 isolated tests pass on MariaDB; the earlier SQLite baseline has three expected database-specific skips |
| First tenant | Marta Banaszek Atelier-Café: protected historical minimum 267; the 2026-07-19 production audit observed 269 customers and 600 cards, of which 269 were assigned and 331 available |
| Rollout | Additional paying tenants remain disabled until Marta completes the human acceptance checklist |
| Provider acceptance | Brevo, Dotykačka, Google Wallet and SMTP passed on 2026-07-19; Apple Wallet is blocked by its expired Pass Type certificate |
| Commercial/production inputs | Real printer specification, prices, limits, tax/shipping/payment ownership, and historical fulfillment ranges still require approval |
The current rollout work is tracked in PLAN.md. The completed Phase 0–11 implementation history is preserved in docs/archive/saas-conversion-plan-phases-0-11.md. Phase 11 evidence and the first-tenant checklist are in docs/phase-11-production-hardening.md and docs/runbooks/marta-acceptance.md. The live production audit is in docs/phase-12-marta-launch-acceptance.md.
The product has two deliberately separate operating surfaces:
- The tenant portal lets a business manage its brand, card design, registration, Wallet presentation, POS/communications connections, billing view, and print requests.
- The platform operations portal lets MB Studio review proofs, approve and allocate immutable card batches, download protected production packages, and record printing, delivery, or compensating corrections.
| Term | Meaning |
|---|---|
| Tenant | A paying SaaS business, such as Marta Banaszek Atelier-Café |
| Customer or loyalty member | A person receiving that tenant's loyalty card |
| Platform operator | Authorized MB Studio staff operating the central print and SaaS service |
Printing is centralized. A tenant can approve a design and submit a request, but only a platform superuser can download a production package or record production and fulfillment. Historical events are append-only; corrections do not rewrite the original event.
- Never drop, truncate, reset, silently overwrite, or broadly delete database records, customer media, generated artifacts, Wallet identities, or historical integration state.
- Every schema or data transition uses a reviewed, forward Django migration.
Migrations must be additive/state-preserving and must never call an external
provider. Historical
dotykackamigrations and tables remain intact. - The approved runtime stack is Python, Django, HTMX and Tailwind CSS. Use ordinary server-rendered HTML as the fallback and JavaScript only for a browser capability that HTMX cannot provide.
- Keep one modular Django project and one primary database by default. Do not add an SPA, Redis, Celery, another runtime, or a payment provider without explicit approval and a demonstrated need. Durable background work uses database-backed jobs and separately supervised Django commands.
- Do not introduce PDF/sheet imposition until a real printer specification proves it necessary. Production formats are versioned and immutable.
- Never render or log API keys, connector secrets, refresh/access tokens, signing keys, customer data, or secret hashes. Tests and migrations must not cause unapproved network, SMTP, Wallet, POS, or production-print side effects.
- Tenant-branded loyalty-card registration by verified domain, explicit slug or globally unique card prefix, with barcode scanning.
- Local customer records in Django/MariaDB.
- Customer creation and synchronization with the Dotykačka Cloud API.
- Apple Wallet
.pkpassgeneration and email delivery. - Google Wallet loyalty-object JWT generation.
- Brevo contact synchronization.
- HTML and plain-text card emails over SMTP.
- Administrative customer listing, pass generation, and bulk delivery.
- Helper scripts for card artwork, barcode images, crops, manifests, and passes.
- Tenant-owned customers, physical-card inventory, branding, users, and integrations.
- Encrypted per-tenant Dotykačka/Brevo credentials and tenant settings UI.
- Shared accessible Django portal shell with separate client and platform navigation.
- Locally served HTMX enhancements and compiled Tailwind CSS with ordinary HTML fallbacks.
- Versioned tenant brand/card design settings with server-rendered proofs.
- Deterministic physical-card artwork, immutable checksummed artifacts, and protected proof downloads.
- Domain-owned tenant/customer/card/artwork services with legacy import and URL compatibility.
- Tenant-scoped immutable master-image library, including both original Marta artwork files, with visual selection in the card-design page.
- Native-resolution deterministic crop v2: sample sheets span the planned series and the full source image, while a bijective position map prevents identical coordinates until the image's pixel-position capacity is exhausted.
- Separate capacity guidance for strongly separated compositions (a quarter-card minimum shift) and exactly unique pixel coordinates, including an explicit warning when the requested card count exceeds either threshold.
- Stable Apple Wallet serials and Google Wallet object identities per tenant customer.
- Dotykačka Connector v2 onboarding with HMAC-signed POST, tenant/session-bound CSRF state, encrypted cloud token cache, 401 refresh and customer reconciliation.
- Consent-gated Brevo upsert/list sync with rate-limit-aware retries and no automatic force-merge.
- Durable tenant-scoped provider jobs claimed by a separately supervised Django worker.
- Immutable billing quotes, entitlement/pack consumption, and append-only physical-production usage.
- Tenant print requests, centralized platform approval/allocation, validated production ZIPs, and append-only fulfillment/corrections.
- Immutable enrollment/consent snapshots, expiring signed Wallet status links, and authorized follow-up retry/resend controls.
- Guarded email-delivery generations that block automatic replay when SMTP has an uncertain outcome.
- Public product, feature, integration, published-pricing, contact and legal pages rendered by Django/Tailwind.
- Read-only public billing projections that exclude drafts and tenant-specific commercial/customer data.
- Idempotent append-only marketing leads with exact versioned consent evidence and ordinary HTML/HTMX form behavior.
- A separately supervised database-backed print worker; no Redis or Celery runtime was added.
- Protected customer media, bounded uploads, database-backed public/connect rate limits, CSP/permissions/privacy headers, request IDs, and redacted JSON logs.
- Public liveness/readiness probes plus a superuser operations console with worker heartbeats and append-only alert acknowledgement/resolution history.
- Transaction-consistent MariaDB/runtime backup commands, checksum manifests, a nightly timer definition, and documented disposable restore drills.
Browser / mobile device
|
Cloudflare + HTTPS
|
Apache + mod_wsgi integration worker print worker operations monitor
| | | |
+-----------------------+----------------------+--------------------+
|
Django 5.2 application
|
+-- MariaDB (domain data, encrypted provider tokens, durable jobs)
+-- Dotykačka Cloud API
+-- Google Wallet API / signed save links
+-- Apple Wallet / OpenSSL-signed .pkpass files
+-- Brevo contacts API
+-- SMTP email
The deployed baseline uses Python 3.11, Django 5.2.17, Apache, and MariaDB 10.11. The source was recovered from the live container on 16 July 2026; no production deployment was changed while creating this repository.
The codebase is a modular monolith. Dependencies point from reusable domain primitives toward provider and workflow implementations, not back into legacy views or higher-level orchestration:
core
└── tenants
├── customers ── cards ── card_artwork
├── integrations
│ ├── pos ── pos_dotykacka
│ └── communications ── brevo
├── wallets ── wallet_apple / wallet_google
└── billing ── printing
enrollment -> explicit domain/provider services and durable jobs
marketing -> read-only projection of published billing data
operations -> read-only health plus append-only monitoring/alert actions
Provider apps implement neutral contracts; provider-specific response shapes
do not leak into tenant, customer, card, billing, or printing models. Billing
does not import printing, and card artwork does not fulfill requests.
Cross-domain workflows use explicit services, transactions and on_commit
handoffs rather than model signals with hidden external effects.
.
├── loyalty_platform/ Active Django settings, root URLs, ASGI/WSGI, and test runner
├── tenants/, customers/ Tenant/customer behavior, external identities, consent evidence
├── cards/, card_artwork/ Card inventory, deterministic renderer, crop plans, and artifact command
├── integrations/ Encrypted credential primitive, durable jobs, settings/worker orchestration
├── pos/, pos_dotykacka/ POS contract and Dotykačka Connector v2/customer adapter
├── communications/, brevo/ Contact contract, email delivery, and Brevo adapter
├── wallets/, wallet_*/ Stable Wallet identities plus Apple/Google implementations
├── billing/, printing/ Entitlements/quotes plus centralized production and fulfillment
├── enrollment/ Tenant registration, consent snapshots, signed links, and follow-up orchestration
├── marketing/ Public site, published-price projection, legal pages, and append-only leads
├── operations/ Security middleware, protected media, health, alerts, heartbeats and backups
├── core/ Extraction inventory and dependency safety rails
├── dotykacka/ Historical model/migration owner and thin compatibility shell
├── assets/css/ Tailwind source CSS
├── turnkey_app/ Historical source only; not installed or imported by active URLs
├── turnkey_project/ Deprecated import compatibility shim
├── templates/ Shared portal/marketing shell and compatibility templates
├── static/ Source fonts, CSS, JavaScript, and images
├── deploy/systemd/ Backup/monitor service and timer definitions
├── docs/runbooks/ Operator, client, billing, fulfillment, restore and acceptance procedures
├── mypass_template/ Non-secret Apple Wallet artwork
├── media/ Runtime data; ignored except for .gitkeep
├── var/logs/ Runtime application logs; ignored
├── add_logo.py Compatibility entry point for the shared card command
├── RandomImageCropper.py Compatibility entry point for deterministic generation
├── generate_pass.py Compatibility entry point for the Wallet command
├── package.json Pinned build-only frontend dependencies and asset commands
└── manage.py Django command entry point
Install Python 3.11 and the system packages needed by Pillow, cryptography, and OpenSSL. Then:
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
npm ci
npm run build
cp .env.example .env
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py createsuperuser
python manage.py runserver
# In another supervised process:
python manage.py run_integration_worker
# In a second supervised process:
python manage.py run_print_worker
# In a third supervised process:
python manage.py run_operational_monitorDevelopment defaults to SQLite and the console email backend. The marketing site
is available at http://localhost:8000/, loyalty registration at
http://localhost:8000/dotykacka/register, and administration at
http://localhost:8000/admin/.
Tenant integrations remain disabled until an authorized tenant owner configures them in the integration settings page. Never copy production credentials into Git.
Node is used only to compile/version static assets. It is not part of the Django
production runtime. The compiled CSS and pinned vendor scripts are committed so
the Apache container can run without Node. See docs/phase-2-portal-shell.md.
The launch interface is entirely Polish and pl is the only enabled language.
Django locale middleware, translation markers in active templates/Python UI
messages, and a project locale directory are already in place so another
language can be added without changing domain data or the frontend stack. The
language selector remains hidden until a complete second catalog is approved.
See docs/localization.md for the catalog workflow and
review rules.
For development against a local copy of the deployed database and media, use the Docker Compose environment. It mirrors Debian 12, Python 3.11, Django 5.2.16, Apache/mod_wsgi, and MariaDB 10.11.11.
The private local replica requires these ignored files copied from the server:
.env
local-data/database.sql.gz
local-data/media/
local-data/mypass_template/
secrets/google-wallet-service-account.json
Start it with:
docker compose up --build -d
docker compose psOpen http://localhost:8000/. MariaDB is available to local database tools at
127.0.0.1:3307 with the application credentials from .env.
The SQL dump is imported only when the loyalty-cards_loyalty-db volume is
created for the first time. To import a fresh copy later, stop the stack and
remove that named volume before starting again. Removing the volume permanently
deletes the local database copy, so confirm the target carefully.
Caution
The copied environment contains production integration credentials. Actions performed locally can create real Dotykačka customers, change Brevo contacts, generate live Wallet objects, and send real email. Do not use real customer addresses for development tests unless that external effect is intentional.
The production deployment uses MariaDB, so it also needs the MySQL client headers and driver:
python -m pip install -r requirements-production.txtThe Apple Wallet generator calls the openssl and zip command-line tools.
Apache must load loyalty_platform.wsgi from the project virtual environment
and serve only collected static assets directly. Runtime media is authorized by
Django; do not add a broad Apache /media/ alias.
GitHub Actions runs the full isolated CI suite on every pull request and push to
main, including a MariaDB 10.11 job matching production. After the exact
main commit passes CI, the production workflow can deploy it through a
dedicated least-privilege SSH account.
The deployment uses versioned releases, keeps production secrets and runtime data only on the server, creates a verified backup before migration, validates before/after tenant aggregates, switches Apache atomically, supervises all workers with systemd, checks public health, and restores the previous code if startup fails. See docs/runbooks/deployment.md for bootstrap, GitHub settings, release flow and rollback instructions.
Copy .env.example to .env and configure the platform-owned groups:
| Group | Important variables |
|---|---|
| Django | DJANGO_SECRET_KEY, DJANGO_DEBUG, DJANGO_ALLOWED_HOSTS, DJANGO_CSRF_TRUSTED_ORIGINS, APP_BASE_URL |
| Database | DB_ENGINE, DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT |
EMAIL_BACKEND, EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, DEFAULT_FROM_EMAIL |
|
| Tenant-secret encryption | TENANT_SECRETS_ENCRYPTION_KEYS |
| Integration transport | INTEGRATION_HTTP_RETRIES, DOTYKACKA_HTTP_TIMEOUT, BREVO_HTTP_TIMEOUT |
| Dotykačka platform | DOTYKACKA_CONNECTOR_CLIENT_ID, DOTYKACKA_CONNECTOR_CLIENT_SECRET, DOTYKACKA_CONNECTOR_URL, DOTYKACKA_API_BASE_URL |
| Google Wallet platform | GOOGLE_WALLET_SERVICE_ACCOUNT_FILE, GOOGLE_WALLET_SERVICE_ACCOUNT_EMAIL, GOOGLE_WALLET_ISSUER_ID, GOOGLE_WALLET_ORIGINS, GOOGLE_WALLET_API_BASE_URL |
| Apple Wallet | APPLE_WALLET_PASS_TYPE_IDENTIFIER, APPLE_WALLET_TEAM_IDENTIFIER |
| Protected storage | MEDIA_ROOT, PRINT_PACKAGE_ROOT (print packages must remain outside media and all web-server aliases) |
| Enrollment | ENROLLMENT_LINK_TTL_DAYS (finite signed status-link lifetime; default 30) |
| Public marketing/legal | MARKETING_LEGAL_NAME, MARKETING_LEGAL_ADDRESS, MARKETING_CONTACT_EMAIL, MARKETING_PRIVACY_VERSION, MARKETING_TERMS_VERSION |
| HTTPS/security | DJANGO_SECURE_SSL_REDIRECT, secure-cookie/HSTS flags, DJANGO_TRUST_X_FORWARDED_PROTO, LOYALTY_TRUSTED_PROXY_CIDRS, upload bounds and rate limits |
| Operations | heartbeat/monitor thresholds, entitlement/inventory warnings, retention review and BACKUP_ROOT |
Configuration ownership is intentional:
| Owner | Configuration |
|---|---|
| Platform environment/secret store | Django/database/SMTP settings; tenant-secret encryption keys; Dotykačka Client Application ID and secret; Google issuer and signing service account; Apple identifiers and signing material; storage and operational settings |
| Encrypted tenant database settings | Dotykačka Connector-returned authorization/Refresh Token, locked Cloud ID and discount group; Brevo API key, list ID and default phone country; integration enablement and safe status metadata |
| Ordinary tenant database settings | Tenant identity, brand, card design, domain/prefix, locale and other non-secret business configuration |
For production, use DJANGO_DEBUG=False, secure cookies, HTTPS redirect, the
real public origin, and the MariaDB configuration. The legacy host’s
/var/lib/django/allowed_hosts file is read automatically when present; its
path can be overridden with LOYALTY_ALLOWED_HOSTS_FILE.
TURNKEY_ALLOWED_HOSTS_FILE remains a deprecated fallback for one release.
Dotykačka Refresh Token, cloud ID and discount group; and Brevo list ID, API
key and default phone country are tenant-owned database settings. The Refresh
Token returned by Connector is encrypted on its tenant connection and exchanged
with that tenant's Cloud ID for a short-lived, cloud-scoped access token. It is
never rendered after authorization. Google Wallet uses the
platform issuer from GOOGLE_WALLET_ISSUER_ID; a tenant's class identity is
derived internally from its unique card prefix, while visible Wallet content
comes from tenant branding. Migration 0010 reads old environment values once
to initialize Marta. Migration 0014 promotes Marta's historical encrypted
authorization value to the canonical tenant refresh_token key without
deleting the legacy value. Runtime tenant settings then read database records. See
docs/phase-1-tenant-configuration.md for the ownership boundary and key
rotation rules.
DOTYKACKA_CONNECTOR_CLIENT_ID and DOTYKACKA_CONNECTOR_CLIENT_SECRET are the
two platform Client Application credentials issued by Dotykačka after
registration. One Client Application can connect multiple independent tenant
clouds. These values are not interchangeable with a Cloud ID or the
authorization/Refresh Token returned after a tenant logs in and approves its
cloud. The system-connections check names a missing environment variable
without exposing any credential value.
Superusers can run redacted, non-destructive infrastructure checks at
/dotykacka/platform/system-connections. Google Wallet is tested with a
read-only issuer request; SMTP authenticates without sending a message; Apple
Wallet validates signing material; active Dotykačka clouds are tested with
their encrypted tenant Refresh Tokens and database-owned Cloud IDs; and active Brevo
connections are tested from their encrypted database credentials.
Dotykačka onboarding lives on each tenant's integration settings page. An authorized tenant owner starts the browser-based Connector flow, logs into Dotykačka and selects the company cloud. The resulting Cloud ID remains locked and cannot be edited or replaced until that tenant explicitly disconnects the integration. Connector credentials and Refresh Tokens are never rendered.
- The first tenant's display brand is Marta Banaszek Atelier-Café. Its legal billing entity is CENTRUM CONCEPT SPÓŁKA Z OGRANICZONĄ ODPOWIEDZIALNOŚCIĄ; display and legal names must not be silently merged.
- Card prefixes are globally unique. Marta retains the historical
MBprefix. - The local database is the business source of truth. External provider IDs, synchronization results and retry state are mirrored locally and remain auditable.
- A platform user can belong to more than one tenant, with an explicit role per membership. Tenant queries and downloads always require tenant scoping.
- Apple and Google Wallet issuer/signing accounts are platform-owned. The card's visible brand, colors, text and customer data come from an immutable tenant design snapshot.
- No price, plan assignment, tax/shipping rule, production cost, payment provider, invoice status, or legacy delivery event may be invented. These are published or appended only from an approved commercial/operator decision.
- Marta's historical
MB-1..600production and delivery state remains unchanged until the operator confirms exact ranges, dates and references through the no-write reconciliation preview.
Google Wallet expects its service-account JSON at the path configured by
GOOGLE_WALLET_SERVICE_ACCOUNT_FILE. The default is:
secrets/google-wallet-service-account.json
Apple Wallet signing expects these files at runtime under
media/mypass_template/:
AppleWWDR.pem
certificate.pem
key.pem
icon.png
icon@2x.png
logo@2x.png
Signing keys, service-account JSON, generated .pkpass files, customer images,
and the entire production media/ tree must be provisioned separately. They
must never be committed.
Production print ZIPs are stored separately under PRINT_PACKAGE_ROOT (default
var/print-packages) and streamed only by the audited superuser view. Never put
that root below MEDIA_ROOT or expose it with an Apache/static-media alias.
See docs/apple-wallet-certificate-renewal.md for the Pass Type ID certificate
renewal and deployment procedure.
| Method | Path | Access | Purpose |
|---|---|---|---|
GET |
/ |
Public | Marketing homepage and published-plan summary |
GET |
/funkcje/ |
Public | Product features and operating workflow |
GET |
/integracje/ |
Public | Supported POS, Wallet and communication capabilities |
GET |
/cennik/ |
Public | Active published plans and public production pricing |
GET, POST |
/kontakt/ |
Public | Consent-gated, idempotent contact lead form |
GET |
/polityka-prywatnosci/, /regulamin/ |
Public | Versioned legal pages |
GET |
/turnkey/, /marketing/ |
Public | Permanent compatibility redirects to / |
GET, POST |
/dotykacka/register |
Public | Register a loyalty customer |
GET, POST |
/dotykacka/c/<tenant-slug>/register |
Public | Tenant registration |
GET |
/dotykacka/enrollment/status/<signed-token> |
Public capability link | Expiring redacted Wallet and follow-up status |
GET |
/dotykacka/enrollment/status/<signed-token>/apple-pass |
Public capability link | Download the prepared Apple pass before link expiry |
GET |
/accounts/login/ |
Public | Client portal login |
GET |
/health/live, /health/ready |
Public | Redacted supervisor/load-balancer liveness and readiness |
GET |
/dotykacka/c/<tenant-slug>/portal |
Tenant member/platform superuser | Client dashboard |
GET |
/dotykacka/c/<tenant-slug>/enrollments |
Tenant owner/platform superuser | Enrollment, domain-request and durable follow-up history |
POST |
/dotykacka/c/<tenant-slug>/enrollments/<id>/* |
Tenant owner/platform superuser | Ensure missing jobs, retry a failed provider job, or explicitly resend email |
GET |
/dotykacka/c/<tenant-slug>/billing |
Tenant owner/platform superuser | Subscription, usage, packs and immutable quote history |
POST |
/dotykacka/c/<tenant-slug>/billing/quotes |
Tenant owner/platform superuser | Calculate allowance → pack → tier → shipping/tax (HTMX or normal POST) |
POST |
/dotykacka/c/<tenant-slug>/billing/quotes/<id>/accept |
Tenant owner/platform superuser | Accept and freeze a quote and reserve proposed pack quantities |
GET |
/dotykacka/c/<tenant-slug>/printing |
Tenant owner/platform superuser | Review and submit print requests from an accepted quote and published proof |
POST |
/dotykacka/c/<tenant-slug>/printing/requests |
Tenant owner/platform superuser | Submit one idempotent immutable print request |
GET, POST |
/dotykacka/c/<tenant-slug>/settings/integrations |
Tenant owner/platform superuser | Configure tenant integrations |
POST |
/integrations/dotykacka/<tenant-slug>/connect |
Tenant owner/platform superuser | Start the tenant-authorized Connector v2 POST flow |
GET |
/integrations/dotykacka/callback |
Initiating tenant owner/platform superuser | Verify state and store the encrypted Refresh Token and locked Cloud ID |
POST |
/integrations/dotykacka/<tenant-slug>/disconnect |
Tenant owner/platform superuser | Disable the integration, clear active authorization and unlock Cloud selection without deleting history |
POST |
/dotykacka/c/<tenant-slug>/settings/integrations/<provider>/test |
Tenant owner/platform superuser; Dotykačka platform-only | HTMX/plain-HTML connection test |
GET, POST |
/dotykacka/c/<tenant-slug>/settings/card-design |
Tenant owner/platform superuser | Select/upload a master, calculate unique-crop capacity, generate proofs and publish immutable design versions |
GET |
/dotykacka/c/<tenant-slug>/artwork-sources/<id>/preview |
Tenant owner/platform superuser | Authorized, size-bounded preview of an immutable master image |
GET |
/dotykacka/c/<tenant-slug>/artifacts/<id>/download |
Tenant owner/platform superuser | Protected proof/artifact download |
GET |
/dotykacka/platform/print-center |
Platform superuser | Filtered centralized request queue, inventory and legacy no-write preview |
GET, POST |
/dotykacka/platform/print-center/requests/<id>[/*] |
Platform superuser | Review, approve/reject, allocate, monitor, fulfill or compensate a print request |
GET |
/dotykacka/platform/print-center/packages/<id>/download |
Platform superuser | Download an audited, size/checksum-validated immutable production ZIP |
GET, POST |
/dotykacka/platform/billing |
Platform superuser | Publish plan/price versions and assign subscriptions/packs |
GET, POST |
/dotykacka/platform/operations |
Platform superuser | Detailed health, safe provider configuration and append-only alert handling |
GET |
/admin/ |
Staff | Django administration |
GET, POST |
/admin/customers/customerexportapitoken/ |
Authorized staff/tenant owner | Create, review and revoke tenant customer-export tokens |
GET |
/api/v1/clients/ |
Tenant bearer token | Paginated clients with email addresses |
GET |
/dotykacka/customers |
Superuser | Customer and card operations |
POST |
/dotykacka/send_pass/<barcode> |
Superuser | Send one customer's passes |
POST |
/dotykacka/add_all_to_brevo |
Superuser | Synchronize contacts to Brevo |
POST |
/dotykacka/generate_jwt_passes |
Superuser | Refresh Google Wallet save links |
POST |
/dotykacka/send_passes_to_all |
Superuser | Bulk email all customer passes |
The legacy access-token diagnostic route is restricted to superusers and never renders the token value.
The production API is read-only. It exports only customers belonging to the tenant assigned to the bearer token, and it does not provide create, update or delete operations.
| Method | Production URL | Purpose |
|---|---|---|
GET |
https://club.mbstudio.online/api/v1/clients/ |
Return the first page of customers that have an email address |
GET |
https://club.mbstudio.online/api/v1/clients/?page=<number>&page_size=<size> |
Return a selected page; page_size defaults to 100 and may not exceed 500 |
GET, POST |
https://club.mbstudio.online/admin/customers/customerexportapitoken/ |
Create and revoke tenant-scoped API tokens in Django admin |
- Sign in to Django admin and open Klienci → Tokeny API eksportu klientów.
- Select Dodaj token API eksportu klienta.
- Choose the tenant, for example Marta Banaszek, and enter a descriptive token name for the consuming system.
- Save and immediately copy the displayed
lst_live_...token. The raw token is shown once and cannot be recovered later.
Each token is bound to one tenant, so a consuming system can read only that business's customers. The database stores only the token's SHA-256 digest and a visible prefix. The admin list records its last use and provides the Unieważnij wybrane aktywne tokeny action. Creation and revocation are written to the platform audit history.
Send the token in the HTTP Authorization header. Do not put it in the URL:
curl --fail-with-body \
-H "Accept: application/json" \
-H "Authorization: Bearer lst_live_REPLACE_WITH_TOKEN" \
"https://club.mbstudio.online/api/v1/clients/?page=1&page_size=100"The response contains the complete safe customer profile stored by the
platform: client_id, first_name, last_name, phone, and email, plus
count, page, page_size, next, and previous pagination metadata. The
database does not contain customer address or birth-date fields. Internal
database/tenant identifiers and signed Wallet URLs are not exposed. Records
without an email address are excluded. Continue requesting the URL in next
until it is null to import every page. Treat client_id as the stable external
identifier when updating an existing record in the consuming system.
Example result:
{
"count": 1,
"page": 1,
"page_size": 100,
"next": null,
"previous": null,
"results": [
{
"client_id": "MB-12",
"first_name": "Anna",
"last_name": "Nowak",
"phone": "+48501234567",
"email": "anna@example.com"
}
]
}| HTTP status | Meaning |
|---|---|
200 |
Page returned successfully |
400 |
Invalid page or page_size |
401 |
Token is missing, invalid, revoked, or belongs to an inactive tenant |
404 |
Requested page does not exist |
405 |
Method other than GET was used |
429 |
Rate limit reached; wait for the number of seconds in Retry-After |
All API responses disable caching. Store the token as a secret, never commit it to source control or log it, use HTTPS only, and revoke it in admin immediately if it is exposed or the consuming system no longer needs access.
python manage.py check
python manage.py check --deploy --fail-level WARNING
python manage.py makemigrations --check --dry-run
python manage.py migrate --plan
python manage.py verify_app_extraction --strict --expect-marta
python manage.py verify_saas_rollout --expect-marta
python manage.py test --noinput
python manage.py run_print_worker --once
python manage.py run_operational_monitor --once
python manage.py create_platform_backup --label scheduled
python manage.py verify_platform_backup /protected/path/manifest.json
npm ci
npm run build
pip-audit -r requirements-production.txt
npm audit --audit-level=highUse --expect-marta only on the protected first-tenant replica. Phase 11
evidence and the remaining human rollout gate are in
docs/phase-11-production-hardening.md and
docs/runbooks/marta-acceptance.md.
Tests use an isolated database, block unmocked network/SMTP calls, and cover the legacy behavior plus tenant migration, authorization, encryption, isolation, portal fallbacks, pinned static assets, deterministic card output, immutable artifact retries, Connector state/401/429 behavior, Brevo consent/duplicates, durable worker recovery, Wallet identity, and cross-tenant download denial. Billing coverage adds seat/card limits, usage idempotency, allowance/pack/tier boundaries, decimal/currency calculations, role isolation, and frozen quotes. Printing coverage adds request/job idempotency, frozen proof/design/quote/layout snapshots, transactional allocation and quote consumption, manifest/file checksums, platform authorization, controlled fulfillment/corrections and legacy no-write preview. Enrollment coverage adds domain/slug/prefix tenant resolution, locked card assignment, frozen brand and consent evidence, post-commit follow-ups, expiring signed links, tenant-isolated retry/resend actions and ambiguous-email replay protection. Marketing coverage adds active/latest-published filtering, draft and tenant-data exclusion, truthful empty pricing, ordinary/HTMX form paths, idempotent consent evidence, append-only enforcement, legal configuration checks and direct legacy redirects. Operations coverage adds safe headers/log redaction, authorization-aware media, rate limits, redacted health, alert lifecycle, heartbeat handling and backup checksum/archive verification. The complete suite currently contains 228 tests.
Safe bounded generator commands replace the former standalone loops:
python manage.py generate_card_artifacts \
--tenant marta-banaszek-atelier-cafe --start 1 --end 10 --dry-run
python manage.py generate_wallet_passes \
--tenant marta-banaszek-atelier-cafe --start 1 --end 10 --wallet apple --dry-run
python manage.py verify_card_design_backfillRemove --dry-run only after reviewing the tenant, design version, selected
codes, and count. Every run publishes to a new tenant/design/batch/run path;
existing artifacts are never overwritten. See docs/phase-3-card-designs.md.
- Customer names, phone numbers, email addresses, barcodes, images, card files, and bulk-send logs are personal or operational data and stay outside Git.
- Historical plaintext-compatible
AccessTokenrows are retained unchanged and hidden from Django admin/templates. All new runtime Dotykačka access tokens use encrypted, expiring, cloud-scopedDotykackaAccessTokenrecords. - All administrative and bulk routes must remain superuser-only and POST-only where they mutate state.
- Rotate any production credential that has previously appeared in source, logs, shell output, or an older repository history.
- Use a secret manager or root-readable environment file on the host; never store
live values in
.env.example. - Back up MariaDB and runtime media separately and encrypt those backups.
| Phases | Delivered outcome |
|---|---|
| 0–3 | Safety tests, tenant/Marta foundation, Django/HTMX/Tailwind portal, versioned designs and unified bounded generators |
| 4–5 | Project rename safety rails and extraction of tenants, customers, cards and artwork into owned Django domains |
| 6–7 | Provider-neutral POS/communications/Wallet integrations, encrypted tenant credentials, durable jobs, billing, entitlements and immutable pricing |
| 8–9 | Central print request/production/fulfillment and durable tenant enrollment/follow-up workflows |
| 10–11 | Public marketing/published pricing plus production security, monitoring, backups, CI, restore drills and rollout runbooks |
The detailed completed plan, including acceptance evidence and preserved decisions, is archived at docs/archive/saas-conversion-plan-phases-0-11.md. The active PLAN.md begins with Phase 12: Marta acceptance, commercial and printer certification, a second-tenant pilot, and measured compatibility retirement. Technical rollout checks pass, but launch still requires the human and provider gates described there.
Commit source code, migrations, templates, non-secret static artwork, requirements, documentation, and sanitized configuration examples.
Do not commit .env, virtual environments, nested .git directories,
databases, media, Google service-account files, Apple signing material,
generated Wallet passes, customer exports, logs, or backups.