Backend¶
Django REST Framework application in backend/. It owns the domain model, the Twilio
integration, billing, and the Firestore mirror that feeds live updates to the clients.
Stack¶
- Python 3.11 (
backend/Dockerfile:2) - Django + DRF +
djangorestframework-simplejwt, served over ASGI byuvicorn settings.asgi:applicationon port 8000 (backend/Dockerfile:31) twilio,stripe,firebase-admin,django-minio-storage,django-auditlog- PostgreSQL; MinIO/S3 for media
Dependencies are unpinned
backend/requirements.txt pins only pytest and hypothesis. Every other package —
Django included — resolves to whatever is current at image build time. The settings
docstring mentions Django 4.2.2 (backend/settings/settings.py:4), but that is the
version the file was generated with, not a constraint. Two builds a month apart can
ship different Django minors.
Configuration¶
There is one settings module, settings/settings.py (429 lines), read through
python-decouple's config(). DJANGO_SETTINGS_MODULE=settings.settings
(backend/manage.py:9). There are no per-environment settings modules — environments
differ only by environment variables.
In production, backend/entrypoint.sh loads around fifty values from Docker Swarm
secrets named TWTLNKPROD_* (only when the variable is not already set), then runs
collectstatic and migrate --noinput before handing off to the command
(backend/entrypoint.sh:30-98). That TWTLNKPROD_* list is the authoritative inventory of
production configuration keys.
See Configuration for the variable reference.
Application layout¶
INSTALLED_APPS contains exactly one project app: voip
(backend/settings/settings.py:55-71). calls/ and google_hooks/ have an apps.py but
are not installed — they are URL and logic modules with no models.
Root URL map (backend/settings/urls.py:6-12):
| Prefix | Module | Responsibility |
|---|---|---|
admin/ |
Django admin | Staff tooling |
api/v1/ |
voip.urls |
The whole authenticated REST API |
webhooks/ |
voip.urls_wh |
Current Twilio webhooks (calls and SMS) |
twilio/hooks/ |
calls.urls |
Legacy hooks — token, record, voicemail; most routes commented out |
google/hooks/ |
google_hooks.urls |
Cron triggers |
Inside voip/ there are roughly thirty sub-packages — company, companyUser, crm,
sip, calls, messages, contact, notifications, redirects, stateTrace,
twilio_charges, stripe_suscriptions and more — plus 105 migrations.
Two of them, voip/calls and voip/messages, follow a hexagonal layout (domain/,
application/, infrastructure/, adapters/). The rest are flat api.py / models.py /
features.py / serializer.py. Expect both styles.
backend/tools/ is not a Django app — it holds standalone operator scripts
(set_webhooks.py, manual_twilio_charges_update.py, compare_charges.py).
Domain model¶
The models that carry the system:
| Model | Location | Purpose |
|---|---|---|
User |
voip/user/models.py:45 |
AUTH_USER_MODEL; email is the username. twilio_call_id is client:<email> |
Company |
voip/company/models.py:12 |
The tenant. Holds record_calls / transcript_calls; isAdmin=True marks the super-tenant |
CompanyUser |
voip/companyUser/models.py:7 |
User ↔ Company join carrying role and is_default — the pivot of tenancy and authorization |
PhoneNumber |
voip/phoneNumber/models.py:6 |
A Twilio DID owned by a company; MAIN or REDIRECT, with priority |
Contact |
voip/contact/models.py:26 |
Per-company address book; drives caller ID and call language |
Call |
voip/crm/models.py:34 |
One Twilio call: sid, status, direction, duration, income, caller and responder |
CallRecord |
voip/crm/models.py:137 |
Recording metadata plus storage and billing math |
CallTranscription |
voip/crm/models.py:202 |
Whisper transcript and its per-minute price |
CallTrace |
voip/call_trace/models/__init__.py:11 |
Live per-agent call state; FK to a User or a SipCredential |
CallConfData / CallConfCalleeData |
voip/call_trace/models/call_conf_data.py:7,28 |
Conference identity and each callee leg |
SipDomain |
voip/sip/models/domains.py:14 |
Twilio SIP domain: subdomain, voice URL, registration flag |
SipCredentialsList |
voip/sip/models/credentials_list.py:18 |
Twilio credential list and its mapping SIDs |
SipCredential |
voip/sip/models/credentials.py:14 |
One SIP username/password — a hardphone agent |
StateTrace / HeartBeat |
voip/stateTrace/models.py:9,38 |
Agent online/offline presence, per platform |
Message |
voip/messages/models.py:18 |
SMS with Twilio sid, status, price and segment count |
AttentionHours |
voip/companyServices/models/attention_hours.py:10 |
Per-day open/close windows, closing messages, whitelist |
CompanyRedirects / RulesGroup / Rule |
voip/redirects/models.py |
JSON-parameter rule engine for call redirection |
Stripe_susc |
voip/stripe_suscriptions/models.py:6 |
One Stripe subscription per phone number |
TwilioCallRecord and siblings |
voip/twilio_charges/models/ |
Raw Twilio billing mirror keyed by SID, for reconciliation |
django-auditlog tracks User, Company, Contact, CompanyUser, Call,
PhoneNumber, StateTrace, Extension and NewsLetter (voip/models.py:13-21).
Authentication and authorization¶
Authentication is JWT only: DEFAULT_AUTHENTICATION_CLASSES is
[JWTAuthentication] (backend/settings/settings.py:174-176). Access tokens last one day,
refresh tokens five, with rotation and blacklisting enabled, HS256
(backend/settings/settings.py:191-211).
| Endpoint | Purpose |
|---|---|
POST /api/v1/login |
Email + password |
POST /api/v1/login-firebase |
Verify a Firebase ID token, map it to a FirebaseUser |
POST /api/v1/login/refresh |
Rotate the access token |
POST /api/v1/tokenverify |
Validate a token |
POST /api/v1/logout |
Blacklist the refresh token |
Password reset is a three-step emailed-token flow.
DEFAULT_PERMISSION_CLASSES is not configured
DRF therefore falls back to AllowAny, and every protected view has to opt in by
declaring permission_classes itself. Any view that forgets is public. Audit new
views for this explicitly; see Known issues.
Roles live on CompanyUser.role: Owner, Admin, Staff
(voip/companyUser/models.py:8-18). The permission classes that enforce them are
IsAdminCompanyUser, IsAdminOrStaffCompanyUser and IsOwner
(voip/companyUser/permissions.py), plus IsAdminCompany and
CompanyViewIsAdminCompanyOrAdminUser (voip/company/permissions.py) which gate the
super-tenant.
Twilio requests are authenticated by IsTwilio
(backend/settings/twilio/permissions/is_twilio.py:9), which validates
X-Twilio-Signature against a set of candidate URLs so that proxy rewrites do not break the
signature check.
Integrations¶
- Twilio — Programmable Voice and TwiML, Voice SDK access tokens, Elastic SIP domains and credential lists, Queues, Messaging, push credentials, and Usage Records for billing reconciliation. Covered in Telephony.
-
Firebase — FCM push notifications, Firestore as the live-update channel, and Firebase Auth for social login. Credentials come from
PUSH_NOTIFICATIONS_CREDENTIAL(backend/settings/firebase/__init__.py:9).Note
initialize_firebase()begins with an unconditionalreturn(backend/settings/firebase/firebase_init.py:7). The real initialisation happens as an import side effect ofsettings/firebase/__init__.py, pulled in fromsettings/__init__.py:5. Do not "fix" the early return without tracing both paths. -
Stripe — one subscription per phone number, metered usage events (
incoming-message,outgoing-message,incoming-call-min,outgoing-call-min,storing-calls), a billing portal at/api/v1/stripe/portaland a webhook at/api/v1/stripe/webhook. - MinIO/S3, Whisper and Graylog, as described in Architecture overview.
Running it¶
See Getting started for the local loop and Deployment for how images reach the servers.