Skip to content

Known issues

Things that are true about this codebase today and will surprise you. Each one is either a trap for a newcomer or a risk worth tracking. None of them are opinions about style.

Secrets in git history

backend/google_credentials.json — a live Google service-account private key — and frontend/towitlink.jks, the Android release signing keystore, were both tracked in this repository. They have been removed from the index, but removal does not erase history: anyone with a clone from before the removal still has them.

Required follow-up:

  1. Rotate the Google service-account key in Google Cloud.
  2. Place the keystore manually on the Android build host, which gets its copy from a checkout of this repository and will lose the file on the next pull.
  3. Decide what to do about the keystore itself. frontend/android.md:22 also documents its password in plain text, and a published app's signing key cannot be rotated without Play App Signing.

Celery is configured but does not exist

requirements.txt installs Celery, backend/docker-compose.yml and docker-compose-dev.yml run celery -A settings worker and ... beat, and entrypoint.sh loads CELERY_BROKER. There is no task code anywhere in the backend — no settings/celery.py, no shared_task, no beat_schedule. Those containers cannot do anything.

The batch jobs run instead as unauthenticated HTTP endpoints under /google/hooks/, called by something outside the repository:

Endpoint Job
POST /google/hooks/reminders Due note reminders become notifications
POST /google/hooks/records Recording payment and auto-removal
POST /google/hooks/charges Pull Twilio usage into the billing mirror

Open question for the team: who calls /records and /charges, and how often? They mutate billing state and they have no authentication. If the answer is a public scheduler URL, they need a shared-secret check. If nobody knows, that is itself the finding.

DRF has no default permission class

DEFAULT_PERMISSION_CLASSES is not set in backend/settings/settings.py, so DRF falls back to AllowAny. Every protected view has to declare permission_classes itself, and any view that forgets is public to the internet. This is a standing footgun for every new endpoint — review it explicitly in code review.

WebSockets are configured but empty

Django Channels is installed and configured, settings/asgi.py:17 routes a websocket protocol, and both routers return URLRouter([]) (voip/ws_urls.py:3, calls/ws_urls.py:5). settings/ws_middlewares/ws_auth_middleware.py exists and is never imported. On the client, the "WebSocket" comments in MsgCTL.ts:44,110,217 are stale and VITE_WSAPI_URL is read nowhere.

Real-time runs on Firestore. See Architecture overview.

Dependencies are unpinned

backend/requirements.txt pins only pytest and hypothesis. Django, DRF, Twilio, Stripe and firebase-admin all float. Two image builds weeks apart can ship different versions, and a production incident caused by an upstream release would be invisible in the diff.

.env.example files have drifted

The backend example is missing seven variables the code reads and carries one it does not. The frontend example names the Firebase keys with the wrong prefix entirely and lists two variables nothing reads. Both are documented accurately in Configuration; fix the files rather than the documentation when you get the chance.

CI runs no checks and does not deploy the console

No workflow runs tests, linting or type checking, and nothing gates a merge. The two frontend workflows were deleted in b036a2d7, so the web console has no pipeline at all — its deployments are entirely manual. Four repository secrets (FRONTEND_ENV, STAGING_FRONTEND_ENV, PORTAINER_WEBHOOK_VOIP_FRONTEND_DEV, PORTAINER_WEBHOOK_VOIP_NGINX_DEV) exist and are unused.

Only the staging backend redeploys automatically. Every production release is manual.

Dead and misleading files

File Problem
landing/wrangler.jsonc, frontend/wrangler.jsonc Cloudflare Worker configs for a deployment that does not exist; neither app is served by Cloudflare
frontend/frontend.conf Referenced by nothing — only frontend_staging.conf is copied into an image — yet it is the config that describes app.towitlinks.com
frontend/src/lib/shared/constants.ts References a Prisma Part type and /api/maintenance/inventory/parts; carried over from another project and does not type-check as written
backend/calls/urls.py Mostly commented out; the live webhooks are under /webhooks/
frontend/src/lib/fetchWithRefresh.ts Marked @deprecated; use Fetcher
Root Dockerfile Copies frontend/ into the landing image without using it

The production console deploy is a volume rsync

frontend/Dockerfile produces an image that serves nothing. Its entrypoint wipes and refills a shared /voip-frontend volume and then sleeps, while a separate nginx service in the Swarm serves that volume. A deployment is therefore not atomic — nginx serves a mixture of old and new files for the duration of the rsync.

Multi-tenancy rests on a nullable monkey-patch

request.user.defaultCompanyUser() (voip/companyUser/models.py:59) returns the single CompanyUser row flagged is_default=True, and can return None. Several call sites dereference it without a guard (voip/user/features.py:122, voip/dashboard/api.py:17). Separately, the super-tenant mechanism assumes exactly one Company has isAdmin=True; the code says so itself in a FIXME (voip/company/permissions.py:9-10).

Three dead routes in the API

Found while building the route reference:

  • /api/v1/extension/int:extension_id is registered without angle brackets, so no numeric URL ever reaches GetUpdateAndDeleteExtensionView.
  • GetAndCreateStateTracesAPIView is registered on the same /api/v1/stateTrace path as ListAndCreateStateTracesAPIView, which always matches first. Its handlers also expect a state_trace_id kwarg the route never supplies.
  • ListAndCreatePhoneNumberView defines no post despite its name; there is no way to create a phone number through it.

Also: voip/messages/urls_api.py is an empty urlpatterns included at path("/", ...), mounting it at the double-slash /api/v1//.

Two query libraries in the console

@sveltestack/svelte-query@1.6 and @tanstack/svelte-query@5 are both installed. The controller layer uses the former. New code should not reach for the latter until somebody plans the migration.