Files
SigHej/Docs/BACKEND.md

186 lines
4.5 KiB
Markdown
Raw Normal View History

2026-05-12 18:21:25 +02:00
# 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:**
```json
{
"ble_token": "abc123",
"interests": ["devops", "hardstyle", "philosophy"]
}
```
**Response:**
```json
{
"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:**
```json
{
"own_token": "abc123",
"detected_token": "xyz789"
}
```
**Response:**
```json
{
"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):**
```json
{
"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:**
```json
{
"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.
```sql
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
```env
REDIS_URL=redis://localhost:6379
DATABASE_URL=postgresql+asyncpg://user:pass@localhost/sighej
SESSION_TTL_SECONDS=7200
ALLOWED_ORIGINS=http://localhost:3000
```
## Running Locally
```bash
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:
```bash
docker-compose up
```