4.5 KiB
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