Files
SigHej/Docs/BACKEND.md
Henrik Jess Nielsen 99e9b509a0
Some checks failed
Backend CI / test (push) Has been cancelled
Flutter CI / analyze-and-test (push) Has been cancelled
eksplicit mapping af envs
2026-05-12 18:21:25 +02:00

4.5 KiB

Backend — Social Proximity

Technology Stack

Component Choice Reason
Framework FastAPI (Python) Async, typed, WebSocket support, fast to build
Session store Redis Ephemeral data, TTL-based expiry, low latency
Database PostgreSQL Aggregate stats only — structured, durable
Real-time WebSockets (FastAPI native) Push nudge to app without polling

Project Structure

backend/
├── app/
│   ├── main.py               ← FastAPI app init, router registration
│   ├── api/
│   │   ├── session.py        ← POST /session — register BLE token + interests
│   │   ├── match.py          ← POST /match — attempt proximity match
│   │   ├── ws.py             ← WebSocket /ws/{token} — real-time nudge channel
│   │   ├── stats.py          ← GET /stats — anonymised aggregate stats (admin only)
│   │   └── health.py         ← GET /health
│   ├── models/
│   │   ├── session.py        ← Pydantic: SessionCreate, SessionResponse
│   │   ├── match.py          ← Pydantic: MatchRequest, MatchResult
│   │   └── stats.py          ← Pydantic: StatsResponse
│   ├── services/
│   │   ├── session_service.py ← create/get/expire sessions in Redis
│   │   ├── match_service.py   ← interest overlap logic + nudge dispatch
│   │   └── stats_service.py   ← aggregate stats queries (PostgreSQL)
│   └── core/
│       ├── config.py          ← Settings (env vars via pydantic-settings)
│       ├── redis.py           ← Redis connection pool
│       └── database.py        ← SQLAlchemy async engine + session
├── tests/
│   ├── test_session.py
│   ├── test_match.py
│   └── test_stats.py
├── requirements.txt
├── requirements-dev.txt
├── Dockerfile
└── .env.example

API Endpoints

POST /session

Register a new ephemeral session when user enables "open to talk".

Request:

{
  "ble_token": "abc123",
  "interests": ["devops", "hardstyle", "philosophy"]
}

Response:

{
  "session_id": "uuid",
  "expires_at": "2024-01-01T12:00:00Z"
}
  • BLE token stored in Redis with 2-hour TTL
  • Interest list hashed before storage — raw interests never persisted to DB

POST /match

Called when a user's app detects a nearby BLE token.

Request:

{
  "own_token": "abc123",
  "detected_token": "xyz789"
}

Response:

{
  "match": true,
  "shared_interests": ["devops", "hardstyle"],
  "nudge_sent": true
}
  • If match found: WebSocket nudge pushed to both parties
  • Shared interests returned as category labels only — no raw profile data

WebSocket /ws/{ble_token}

Persistent connection from the app. Used to receive real-time nudges.

Nudge message (server → client):

{
  "type": "nudge",
  "shared_interests": ["devops", "hardstyle"],
  "message": "Someone nearby shares your interest in devops and hardstyle."
}

GET /health

Standard health check. Returns 200 OK with service status.


GET /stats

Anonymised aggregate stats for admin dashboard. Internal access only.

Response:

{
  "total_sessions_today": 142,
  "total_matches_today": 38,
  "top_interest_categories": ["tech", "music", "philosophy"],
  "active_sessions_now": 12
}

Data Model (Redis)

Sessions are stored as Redis hashes:

KEY:   session:{ble_token}
VALUE: {
  "session_id": "uuid",
  "interests_hash": "sha256(...)",
  "interest_categories": ["tech", "music"],
  "created_at": "iso8601"
}
TTL:   7200 seconds (2 hours)

Data Model (PostgreSQL)

Only aggregate counters — no user-level rows.

CREATE TABLE daily_stats (
    date          DATE PRIMARY KEY,
    total_sessions INTEGER DEFAULT 0,
    total_matches  INTEGER DEFAULT 0
);

CREATE TABLE interest_category_counts (
    date          DATE,
    category      TEXT,
    count         INTEGER DEFAULT 0,
    PRIMARY KEY (date, category)
);

Environment Variables

REDIS_URL=redis://localhost:6379
DATABASE_URL=postgresql+asyncpg://user:pass@localhost/sighej
SESSION_TTL_SECONDS=7200
ALLOWED_ORIGINS=http://localhost:3000

Running Locally

cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000

Or via Docker Compose from root:

docker-compose up