An achievement-hunter platform that aggregates gaming achievements across platforms. Users sign in with GitHub, link their Steam account, and the platform syncs their achievements in the background β tracking progress, calculating rarity scores, surfacing community guides, and recommending new games.
- GitHub OAuth 2.0 login β no passwords
- Steam account linking via OpenID 2.0
- Asynchronous achievement sync β rate-limited background job with live progress polling
- Dual points system β global rarity (Steam-wide) and community rarity (platform-local)
- Leaderboard β global and friends-only, sortable by either point type
- Activity feed β recent unlocks and guide publications by followed users
- Achievement search β filter by game, query string, or rarity cap
- Milestones β first unlock, game completion, sub-1% rarity achievements
- Guides β per-game markdown guides with header images, descriptions, and favorites
- Game recommendations β vector similarity search powered by pgvector and sentence-transformers
- Wishlist β save recommended games
- Social graph β follow/unfollow users, see followers and following lists
- Avatar options β GitHub avatar, Steam avatar, or custom upload via presigned URL
[ React Frontend (nginx) ]
β :80
β
ββββββββββββββββββββββΌβββββββββββββββββββββ
βΌ βΌ βΌ
/api/user/* /api/achievements/* /api/recommendations/*
β β β
[ User Service ] [ Achievement Service ] [ Recommendation Service ]
FastAPI :8000 FastAPI :8001 FastAPI :8002
β β β
ββββββββββββ¬ββββββββββ β
βΌ β
[ Postgres ] ββββββββββββββββββββββ-β
(pgvector:pg16)
β
ββββββββββββ΄βββββββββββ
βΌ βΌ
[ Redis ] [ MinIO ]
(Celery broker) (avatars, guides)
β
ββββββββββββ΄βββββββββββ
βΌ βΌ
[ Achievement [ Recommendation
Celery Worker ] Celery Worker ]
All four backend containers (two services + two workers) share a single Postgres instance and Redis. The nginx container inside the frontend image proxies all /api/* traffic to the appropriate service over the internal Docker network β the backend ports are never exposed publicly in production.
| Layer | Technology |
|---|---|
| Frontend | Vite, React 19, TypeScript, CSS Modules (SCSS), React Query, React Router |
| Backend | FastAPI, SQLAlchemy 2.0 (async), Alembic, Pydantic |
| Auth | GitHub OAuth 2.0, Steam OpenID 2.0, JWT (httponly cookies) |
| Database | PostgreSQL 16 with pgvector |
| Object Storage | MinIO (S3-compatible) |
| Background Jobs | Celery + Redis |
| Recommendations | sentence-transformers (all-MiniLM-L6-v2), pgvector cosine similarity |
| Containerisation | Docker, Docker Compose |
Chieve/
βββ docker-compose.yml # Local development stack
βββ frontend/
β βββ src/
β β βββ pages/ # Route-level page components
β β βββ components/ # Feature components (guides, feed, games, β¦)
β β βββ api/ # React Query hooks and API helpers
β βββ nginx.conf # Production nginx config (proxy + SPA fallback)
β βββ Dockerfile
βββ backend/
βββ docs/design.md # Full technical design document
βββ User_Service/ # Auth, accounts, social graph (port 8000)
βββ Achievement_Service/ # Games, achievements, guides, feed (port 8001)
βββ Recommendation_Service/ # Vector recommendations, wishlist (port 8002)
- Docker and Docker Compose v2
- A GitHub OAuth App with callback URL set to
http://localhost:5173/api/user/auth/callbackfor local dev (or your production domain for deployment) - A Steam Web API key
git clone https://github.com/JustinMuecke/Chieve.git
cd Chieve# GitHub OAuth
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
# Steam
STEAM_API_KEY=your_steam_api_key
# Postgres
POSTGRES_USER=chieve
POSTGRES_PASSWORD=your_db_password
POSTGRES_DB=chieve
# JWT β must be the same value across all services
JWT_SECRET=your_jwt_secret
# MinIO
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=your_minio_password# backend/User_Service/.env
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=your_minio_passworddocker compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| User Service API docs | http://localhost:8000/docs |
| Achievement Service API docs | http://localhost:8001/docs |
| Recommendation Service API docs | http://localhost:8002/docs |
| MinIO Console | http://localhost:9001 |
| Postgres | localhost:5432 |
| Redis | localhost:6379 |
MinIO credentials: minioadmin / minioadmin (or whatever you set in .env).
cd backend/User_Service
POSTGRES_HOST=localhost uvicorn src.main:app --reload --port 8000Handles authentication, user accounts, platform linking, avatar management, and the social follow graph.
| Method | Route | Description |
|---|---|---|
GET |
/login |
Redirect to GitHub OAuth |
GET |
/auth/callback |
OAuth callback β creates user, sets JWT cookie |
GET |
/steam/link |
Redirect to Steam OpenID (requires auth) |
GET |
/steam/callback |
Validate Steam assertion, save SteamID |
GET |
/me |
Current user profile |
PUT |
/me/avatar |
Set active avatar source (github, steam, custom) |
POST |
/me/avatar/upload |
Get presigned PUT URL for custom avatar upload |
POST |
/social/follow/{id} |
Follow a user |
DELETE |
/social/follow/{id} |
Unfollow a user |
GET |
/social/following |
Users I follow |
GET |
/social/followers |
Users following me |
Manages games, achievement records, the leaderboard, activity feed, search, milestones, and guides.
| Method | Route | Description |
|---|---|---|
POST |
/sync |
Enqueue a Steam sync (rate-limited to 1 per 15 min) |
GET |
/sync/{task_id} |
Poll sync progress |
GET |
/games |
User's synced games with completion % |
GET |
/games/{app_id} |
Game detail + full achievement list |
GET |
/profile |
Own stats (achievements, global points, community points) |
GET |
/profile/{user_id} |
Another user's public stats |
GET |
/leaderboard |
Global or friends leaderboard |
GET |
/feed |
Recent activity from followed users |
GET |
/search |
Achievement search |
GET |
/milestones |
Own milestones |
GET |
/milestones/{user_id} |
Another user's milestones |
GET |
/guides/{app_id} |
Guides for a game |
POST |
/guides/{app_id} |
Create a guide (multipart: title, file, optional header image + description) |
PUT |
/guides/{app_id}/{guide_id} |
Update own guide |
POST |
/guides/{guide_id}/favorite |
Favorite a guide |
DELETE |
/guides/{guide_id}/favorite |
Unfavorite a guide |
Provides personalised game recommendations via pgvector cosine similarity and manages the wishlist and dismissals.
| Method | Route | Description |
|---|---|---|
GET |
/ |
Personalised game recommendations |
POST |
/dismiss/{app_id} |
Dismiss a game (never show again) |
GET |
/wishlist |
Saved games |
POST |
/wishlist/{app_id} |
Add to wishlist |
DELETE |
/wishlist/{app_id} |
Remove from wishlist |
Chieve uses two independent scoring systems.
Global Points (Steam rarity) Based on how rare an achievement is across all Steam players worldwide. Stored statically per achievement and refreshed when Steam global stats are pulled (every 7 days per game).
global_points = max(10, round(100 - global_unlock_percent))
Community Points (local rarity)
Based on how rare an achievement is among Chieve users specifically. Recalculated daily via a materialized view (mv_community_points) and aggregated into user_profile_stats for fast leaderboard queries.
Each service manages its own Alembic version table and runs migrations automatically on container start.
To generate a new migration locally:
cd backend/User_Service # or Achievement_Service / Recommendation_Service
alembic revision --autogenerate -m "description"To apply migrations manually:
alembic upgrade headVersion tables per service:
| Service | Version table |
|---|---|
| User Service | alembic_version |
| Achievement Service | achievement_alembic_version |
| Recommendation Service | recommendation_alembic_version |
Achievement Celery Worker
platform_sync_task(user_id)β fetches linked platforms from User Service, syncs Steam achievements, upserts profile stats. Triggered on demand viaPOST /sync.refresh_community_pointsβ daily cron at 3 AM, refreshesmv_community_pointsand updatesuser_profile_stats.
Recommendation Celery Worker (combined worker + beat in one container)
generate_embeddingsβ hourly cron, fetches games without embeddings from Achievement Service, generates 384-dimensional vectors usingall-MiniLM-L6-v2, stores them ingame_embeddings.
- Implement
BasePlatformServicefrom backend/Achievement_Service/src/services/platforms/base.py - Register the new class in backend/Achievement_Service/src/services/platforms/dispatcher.py
The Celery sync task calls the dispatcher automatically β no further changes needed.
A docker-compose.deploy.yml is used for production. It is excluded from version control because it contains secrets. To deploy on a fresh server:
# copy and fill in your secrets
cp docker-compose.deploy.yml.example docker-compose.deploy.yml
docker compose -f docker-compose.deploy.yml up -dPre-built images are published to GitHub Container Registry on every push to main:
| Image | Tag |
|---|---|
ghcr.io/justinmuecke/chieve/frontend |
latest |
ghcr.io/justinmuecke/chieve/user-service |
latest |
ghcr.io/justinmuecke/chieve/achievement-service |
latest |
ghcr.io/justinmuecke/chieve/recommendation-service |
latest |
Note: The production compose file does not include SSL termination. Place a reverse proxy (Caddy, nginx, Traefik) in front of port 80 to handle HTTPS before going live. MinIO (port 9000) also needs to be reachable by the browser for media delivery.
The frontend container stores node_modules in an anonymous Docker volume. After adding a new package, --build alone won't pick it up. Remove the container and its volume first:
docker compose stop frontend
# find the node_modules volume hash:
docker inspect chieve-frontend-1
docker rm <frontend_container_id>
docker volume rm <hash>
docker compose up --build frontend