eksplicit mapping af envs
Some checks failed
Backend CI / test (push) Has been cancelled
Flutter CI / analyze-and-test (push) Has been cancelled

This commit is contained in:
Henrik Jess Nielsen
2026-05-12 18:21:25 +02:00
parent b7a435f8b9
commit 99e9b509a0
67 changed files with 8060 additions and 9 deletions

72
Docs/ADMIN.md Normal file
View File

@@ -0,0 +1,72 @@
# Admin Dashboard — Social Proximity
## Purpose
The admin dashboard shows anonymised platform statistics — not to monitor users, but to understand if the nudge concept is working.
**What it shows:**
- How many sessions were started today / this week
- How many matches were made
- Which interest categories are most popular (no user-level breakdown)
- How many active sessions right now
**What it never shows:**
- Who matched with whom
- Where people were
- Individual user data of any kind
## Technology: Next.js (TypeScript)
Simple choice: TypeScript, server-side rendering, easy to deploy. No complex state management needed — stats are read-only and refreshed periodically.
## Project Structure
```
admin/
├── src/
│ └── app/
│ ├── layout.tsx
│ ├── page.tsx ← Main dashboard
│ └── api/
│ └── stats/
│ └── route.ts ← Proxy to backend /stats
├── components/
│ ├── StatCard.tsx ← Single metric card
│ ├── InterestChart.tsx ← Bar chart of top interest categories
│ └── ActivityChart.tsx ← Sessions/matches over time (7 days)
├── package.json
└── .env.local.example
```
## Dashboard Layout
```
┌─────────────────────────────────────────────────────┐
│ Social Proximity — Admin │
├──────────┬──────────┬──────────┬────────────────────┤
│ Sessions │ Matches │ Match % │ Active now │
│ today │ today │ │ │
├──────────┴──────────┴──────────┴────────────────────┤
│ Sessions & Matches — last 7 days (line chart) │
├─────────────────────────────────────────────────────┤
│ Top interest categories (bar chart) │
└─────────────────────────────────────────────────────┘
```
## Environment Variables
```env
BACKEND_URL=http://localhost:8000
```
## Running Locally
```bash
cd admin
npm install
npm run dev # runs on localhost:3000
```
## Access
In MVP the dashboard has no login. It should be deployed behind a reverse proxy with IP restriction or basic auth — not exposed publicly.

129
Docs/APP.md Normal file
View File

@@ -0,0 +1,129 @@
# Mobile App — Social Proximity
## Technology Choice: Flutter
We chose **Flutter** over React Native for this project because BLE scanning requires close hardware access. Flutter compiles to native ARM code and communicates with the platform via direct platform channels — no JavaScript bridge between the app logic and the BLE hardware.
| | Flutter (chosen) | React Native |
|---|---|---|
| Language | Dart | TypeScript |
| BLE access | Platform channels — native | Via JS bridge |
| Performance | Compiles to native | JS runtime overhead |
| BLE library | `flutter_blue_plus` | `react-native-ble-plx` |
| Platforms | Android + iOS from one codebase | Same |
**Dart** is straightforward to learn if you know TypeScript: strongly typed, OOP, async/await, null safety built in.
## BLE Library: `flutter_blue_plus`
`flutter_blue_plus` is the most actively maintained Flutter BLE library. It supports:
- Scanning for nearby BLE peripherals
- Advertising as a BLE peripheral (Android 5+, iOS limited)
- Reading/writing GATT characteristics
- Connection state management
**Required permissions:**
_Android (`AndroidManifest.xml`):_
```xml
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
```
_iOS (`Info.plist`):_
```xml
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Used to detect nearby people with shared interests.</string>
```
## BLE Flow
```
App starts
└─► Check BLE permissions (request if missing)
└─► Generate ephemeral BLE token (UUID v4, per-session)
└─► Register token + interests with backend (POST /session)
User enables "Open to talk"
└─► Start BLE advertising (token in manufacturer data)
└─► Start BLE scanning for other tokens
└─► Open WebSocket to backend (/ws/{token})
Nearby token detected
└─► Send detected token to backend (POST /match)
└─► If match: WebSocket nudge received
└─► Display nudge card to user
User disables "Open to talk" or closes app
└─► Stop advertising + scanning
└─► Close WebSocket
└─► Session expires on backend (Redis TTL)
```
## Project Structure
```
app/
├── lib/
│ ├── main.dart
│ ├── screens/
│ │ ├── onboarding_screen.dart ← Interest selection on first launch
│ │ ├── home_screen.dart ← "Open to talk" toggle + nudge display
│ │ └── settings_screen.dart ← Manage interests, reset session
│ ├── widgets/
│ │ ├── nudge_card.dart ← The nudge notification UI
│ │ ├── interest_chip.dart ← Selectable interest tag
│ │ └── open_toggle.dart ← Big friendly on/off toggle
│ ├── services/
│ │ ├── ble_service.dart ← BLE scan/advertise logic
│ │ ├── api_service.dart ← HTTP client (register, match)
│ │ └── ws_service.dart ← WebSocket client (nudge receiver)
│ └── models/
│ ├── interest.dart
│ ├── session.dart
│ └── nudge.dart
├── android/
├── ios/
└── pubspec.yaml
```
## Screens
### Onboarding (first launch)
- Choose interest categories (multi-select chips)
- Brief explanation of how the app works
- Consent acknowledgement
### Home
- Large "Open to talk" toggle — the primary interaction
- When active: scanning indicator
- Nudge card appears when a match is found
- Shows shared interests (no name, no face, no location)
- "Say hello" is just a reminder — no in-app chat
### Settings
- Manage interest categories
- Reset ephemeral identity
- Privacy information
## State Management
For MVP simplicity: use Flutter's built-in `Provider` or `Riverpod`.
Avoid complex state management (no BLoC in MVP).
## Running Locally
```bash
cd app
flutter pub get
flutter run # runs on connected device or emulator
flutter run -d android
flutter run -d ios
```
**Prerequisites:**
- Flutter SDK ≥ 3.x
- Android Studio (for Android emulator) or Xcode (for iOS simulator)
- Physical device recommended for BLE testing — emulators do not support BLE scanning

97
Docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,97 @@
# Architecture — Social Proximity
## System Overview
Social Proximity er bygget som et minimal monorepo med tre separate komponenter:
```
┌────────────────────────────────────────────────────────┐
│ Mobile App (Flutter) │
│ │
│ BLE Scanner ──► Match Screen ──► "Say hello" nudge │
└───────────────────────┬────────────────────────────────┘
│ HTTPS + WebSocket
┌────────────────────────────────────────────────────────┐
│ Backend (FastAPI) │
│ │
│ Match Engine ──► Session Store (Redis) ──► DB (PG) │
└───────────┬────────────────────────────────────────────┘
│ Internal API
┌────────────────────────────────────────────────────────┐
│ Admin Dashboard (Next.js) │
│ │
│ Anonymised stats — pings, sessions, interest counts │
└────────────────────────────────────────────────────────┘
```
## Components
### Mobile App
- **Technology:** Flutter (Dart) — cross-platform Android + iOS
- **BLE library:** `flutter_blue_plus`
- **Responsibility:** BLE scanning and advertisement, interest profile, consent toggle, nudge display
- **Does NOT:** store data persistently, track location, communicate with other users directly
### Backend
- **Technology:** FastAPI (Python) + WebSockets
- **Session store:** Redis — ephemeral sessions, no long-term identity storage
- **Database:** PostgreSQL — anonymised aggregate stats only
- **Responsibility:** receive BLE advertisement tokens, match users, push nudge via WebSocket, aggregate stats
### Admin Dashboard
- **Technology:** Next.js (TypeScript)
- **Responsibility:** display anonymised platform stats — no PII, no user-level data
- **Access:** Internal only (no public-facing auth in MVP)
## Data Flow
### Happy path — two users nudged to talk
```
User A opens app, selects interests, enables "open to talk"
└─► App generates ephemeral BLE token
└─► App advertises token via BLE
└─► App registers token + interests with backend (POST /session)
User B nearby detects User A's BLE token
└─► App sends token to backend (POST /match)
└─► Backend checks: does B's interest list overlap with A's?
└─► If overlap + both consent: backend sends WebSocket nudge to both
"Someone nearby also works with DevOps and enjoys hardstyle"
└─► Users see nudge, put phones down, say hello
└─► Session expires automatically (Redis TTL: 2 hours)
```
### Privacy properties
- BLE tokens are ephemeral — regenerated every session
- Backend never stores exact location or movement history
- Interest matching happens server-side — apps never see each other's raw profile
- After session expiry, all session data is deleted from Redis
## Infrastructure
| Environment | Platform |
|---|---|
| Local dev | Docker Compose (backend + PostgreSQL + Redis) |
| Production | Azure Container Apps or Kubernetes (i80.dk) |
## Repository Structure
```
SigHej/
├── README.md
├── Docs/ ← Technical documentation (this folder)
├── backend/ ← FastAPI application
├── app/ ← Flutter mobile app
├── admin/ ← Next.js admin dashboard
└── docker-compose.yml
```
See individual docs for each component:
- [BACKEND.md](./BACKEND.md)
- [APP.md](./APP.md)
- [ADMIN.md](./ADMIN.md)
- [TESTING.md](./TESTING.md)
- [DECISIONS.md](./DECISIONS.md)

185
Docs/BACKEND.md Normal file
View File

@@ -0,0 +1,185 @@
# 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
```

78
Docs/DECISIONS.md Normal file
View File

@@ -0,0 +1,78 @@
# Architecture Decision Records — Social Proximity
This file tracks key architectural decisions made during the project.
Each decision includes context, what was decided, and the consequences.
---
## ADR-001: Flutter for the mobile app
**Date:** 2026-05-07
**Status:** Accepted
**Context:**
The app requires BLE scanning and advertising to detect nearby users. We needed a cross-platform framework (Android + iOS) that provides reliable, low-latency access to the BLE hardware stack.
**Decision:**
Use Flutter (Dart) with `flutter_blue_plus`.
Flutter compiles to native ARM code and communicates with the platform via direct platform channels — no JavaScript bridge. This gives more stable and predictable BLE behaviour than React Native's bridge-based architecture. `flutter_blue_plus` is the most actively maintained Flutter BLE library with full support for scanning, advertising, and GATT on both Android and iOS.
**Consequences:**
- Team needs to learn Dart (low barrier — similar to TypeScript)
- BLE integration testing requires physical devices, not emulators
- Build toolchain: Flutter SDK + Android Studio + Xcode
---
## ADR-002: FastAPI for the backend
**Date:** 2026-05-07
**Status:** Accepted
**Context:**
Backend needs to handle short-lived sessions, real-time WebSocket nudges, and serve anonymised stats. Should be fast to build and easy to maintain.
**Decision:**
Use FastAPI (Python) with async support, WebSockets, and Pydantic for request/response validation.
**Consequences:**
- Native async support fits the WebSocket and Redis use cases
- Pydantic models double as documentation and runtime validation
- Python aligns with DevOpsMCP tooling (complexity, type hints, PEP compliance)
---
## ADR-003: Redis for session storage (not PostgreSQL)
**Date:** 2026-05-07
**Status:** Accepted
**Context:**
User sessions are ephemeral by design — they expire after 2 hours and must never be reconstructed. We need fast reads/writes for the match engine.
**Decision:**
Store all active sessions in Redis with a 2-hour TTL. Use PostgreSQL only for aggregate counters (stats).
**Consequences:**
- Sessions are automatically deleted by Redis TTL — no manual cleanup needed
- No user-level data ever reaches the database
- Redis must be treated as infrastructure (not optional) — included in docker-compose
---
## ADR-004: No in-app messaging
**Date:** 2026-05-07
**Status:** Accepted
**Context:**
We want to nudge users into real, face-to-face conversation — not replace it with another chat interface.
**Decision:**
The app will never include a messaging feature. The nudge is the product. After a match, the app's job is done.
**Consequences:**
- Simpler backend (no message storage, no threads)
- Clearer privacy posture
- Risk: lower engagement metrics — accepted by design

103
Docs/TESTING.md Normal file
View File

@@ -0,0 +1,103 @@
# Testing & Code Quality — Social Proximity
## Scope
These rules apply to the **Python backend** (`backend/`). The Flutter app and Next.js admin have separate tooling.
Quality analysis runs via **DevOpsMCP** — a set of code analysis tools accessible through the MCP server at `https://devops-mcp.i80.dk`.
---
## When to Run What
| Tool | Trigger | Threshold |
|---|---|---|
| `check_pep_compliance` | Before every commit | Zero violations |
| `check_personal_standards` | Before every commit | Zero `error`-level findings |
| `analyze_complexity` | When writing functions > 20 lines | Max cyclomatic complexity: **8** |
| `add_type_hints` | When adding new functions | Min type hint coverage: **80%** |
| `analyze_missing_docstrings` | New classes and public functions | No missing docstrings on public API |
| `analyze_yaml` + `analyze_github_actions` | When editing Gitea CI/CD workflow files | Zero security violations |
---
## Coding Standards (Python backend)
### Enforced by `check_personal_standards`
- **No `subprocess` with `shell=True`** — use SDK clients instead
- **No bare `except:`** — always catch specific exceptions
- **No `%` or `.format()` string formatting** — use f-strings
- **No mutable default arguments** — use `None` and set inside function body
- **No hardcoded secrets** — use environment variables via `pydantic-settings`
### Enforced by `analyze_complexity`
- Max cyclomatic complexity per function: **8**
- Functions exceeding this must be refactored (extract helper functions)
### Enforced by `add_type_hints`
- All function parameters must have type annotations
- All function return types must be annotated
- Use `Optional[T]` or `T | None` — not bare `None` returns without annotation
---
## Workflow
```
1. Write code
2. Run check_personal_standards(file) → fix all 'error' findings
3. Run analyze_complexity(file) → refactor if any function > 8
4. Run add_type_hints(file, apply_changes=False) → review suggestions, apply manually
5. Run check_pep_compliance(file) → fix formatting
6. Commit
```
---
## Test Strategy (backend)
Tests live in `backend/tests/` and use **pytest**.
| Layer | What to test | Example |
|---|---|---|
| Unit | Service logic in isolation | `test_match_service.py` — interest overlap calculation |
| Integration | API endpoints with real Redis/DB | `test_session.py` — POST /session creates Redis key |
| Contract | Request/response shapes match Pydantic models | Via pytest + httpx TestClient |
**Run tests:**
```bash
cd backend
pytest tests/ -v
```
---
## Flutter App Testing
| Layer | Tool | Notes |
|---|---|---|
| Unit | `flutter test` | Test service logic, models |
| Widget | `flutter test` + `WidgetTester` | Test UI components in isolation |
| Integration | `flutter drive` | Requires physical device or emulator |
BLE scanning cannot be tested in emulators — use a physical device for BLE integration tests.
---
## DevOpsMCP Quick Reference
```python
# Upload file and run analysis
session = DevOpsMCP-start_project_session("sighej-backend", [
{"file_path": "main.py", "content": "<content>"}
])
DevOpsMCP-check_personal_standards(file_path="main.py")
DevOpsMCP-analyze_complexity(file_path="main.py")
DevOpsMCP-add_type_hints(file_path="main.py", apply_changes=False)
DevOpsMCP-check_pep_compliance(file_path="main.py")
DevOpsMCP-generate_tests(file_path="main.py")
```

21
Docs/colors.md Normal file
View File

@@ -0,0 +1,21 @@
Sunny Beach Day
Golden sand meets turquoise waves under a deep blue sky, kissed by coral and sunlit amber warmth.
Colors
~Charcoal Blue
#264653
Deep navy undertones blend with smoky steely hues for a sophisticated shade evoking midnight skies and modern elegance.
~Verdigris
#2a9d8f
Shimmering between blue and green, evokes serene tropical tides and the calm of sunlit, crystalline waters.
~Jasmine
#e9c46a
Mellow golden hue suggesting gentle charm, balancing gentle sweetness and cheerful sophistication in every space.
~Sandy Brown
#f4a261
Light orangey-brown radiating summer, beach sand, and sunsets, lending softness and energy to compositions.
~Burnt Peach
#e76f51
Fiery, robust shade bursts with energy, merging glowing embers and ripe fruit for a bold, unforgettable impression.
Psychology
Meaning
every