2.4 KiB
sarlinkportal
Monorepo for the SAR Link member portal.
backend/ Django 5.2 + DRF API (knox auth, postgres, procrastinate)
frontend/ Vite + React + TypeScript SPA (served by nginx in production)
.build/prod/ production images and compose
Quick start
cp backend/.env.example backend/.env # add SMS_API_KEY for real texts
docker compose up --build
docker compose exec backend python manage.py createsuperuser # asks for a mobile number
Atolls and islands are seeded by migration. With SMS_API_KEY empty, OTP codes
are printed to the backend log instead of being texted.
| SPA | http://localhost:5173 |
| API | http://localhost:8000/api/ |
| API docs (DEBUG) | http://localhost:8000/api/docs/ |
| Django admin | http://localhost:8000/admin/ |
The root compose.yml just includes backend/compose.yml and
frontend/compose.yml, so each side can also be brought up on its own.
How the two talk
The SPA only ever calls a relative /api/...:
- dev — the vite dev server proxies
/api,/admin,/static,/mediato the backend container. - prod — one nginx serves the built SPA and proxies the same prefixes to gunicorn, so there's a single origin and no CORS.
Authentication
One entry point - the mobile number - and the API decides the second step:
POST /api/auth/start/ {mobile} -> next: "password" | "otp"
password -> POST /api/auth/login/password/ {mobile, password}
-> next: "dashboard" + knox token
otp -> POST /api/auth/verify/ {mobile, code}
-> next: "dashboard" + knox token
-> next: "register" + ticket
-> POST /api/auth/register/ {ticket, ...form} -> pending account
start gives nothing away: a number with no account gets the same "code sent"
response as a member's. Only a confirmed code reveals which it was. New numbers
then go form -> pending, and an admin approves the application in the Django
admin before the account is usable.
backend/README.md documents the responses, OTP/ticket policy, the SMS gateway
and the approval actions; frontend/README.md documents the forms.
Tests
docker compose exec backend python manage.py test --settings=apibase.settings_test
cd frontend && npm run build && npm run lint