[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81392":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":20,"compositeScore":21,"rankGlobal":10,"rankLanguage":10,"license":22,"archived":23,"fork":23,"defaultBranch":24,"hasWiki":25,"hasPages":23,"topics":26,"createdAt":10,"pushedAt":10,"updatedAt":36,"readmeContent":37,"aiSummary":38,"trendingCount":16,"starSnapshotCount":16,"syncStatus":39,"lastSyncTime":40,"discoverSource":41},81392,"geoduels","sourcelocation\u002Fgeoduels","sourcelocation","GeoDuels — A free GeoGuessr alternative with duels, rankings, custom lobbies, and cheater mitigations.","https:\u002F\u002Fgeoduels.io",null,"TypeScript",51,13,1,3,0,4,8,15,12,3.44,"GNU Affero General Public License v3.0",false,"main",true,[27,28,29,30,31,32,33,34,35],"geography","geoguessr","k3s","k8s","maps","postgres","react","redis","streetview","2026-06-12 02:04:14","\u003Cimg src=\"apps\u002Fweb\u002Fpublic\u002Ficon.v1.png\" height=64 \u002F>\n\n# GeoDuels\n\nGeoDuels is a production-ready + dev-ready GeoGuessr-style platform built for horizontal scaling.\n\nhttps:\u002F\u002Fgeoduels.io\u002F\n\n## Architecture\n\n### Runtime topology\n\n- `apps\u002Fweb` (Next.js): browser UI and gameplay shell.\n- `services\u002Fapi` (Go): auth\u002Fsession\u002Fprofile endpoints and backend API surface (`\u002Fv1`).\n- `services\u002Fmatch-coordinator` (Go): matchmaking over websocket (`\u002Fqueue`), assignment, maintenance status, and recovery (`\u002Fv1\u002Fsession\u002Frecover`).\n- `services\u002Frealtime-gateway` (Go): websocket gatewaying (`\u002Fws\u002F{node}`) to the assigned gameplay node.\n- `services\u002Fgameplay-node` (Go): round engine and authoritative match state broadcast for assigned matches.\n- `workers\u002Flocation-ingest` (Go): one-off\u002Fcron worker that validates and ingests location datasets into PostgreSQL.\n\n### Data and state\n\n- PostgreSQL: source of truth for persistent data (profiles, stats, location catalog, match persistence).\n- Redis: queue and distributed coordination state for matchmaking and gameplay node ownership.\n- Dataset JSON files (`datasets\u002F*.json`): seed source for location ingest.\n\n### Network flow\n\n1. Browser loads `web`.\n2. Browser calls `api` for auth + app APIs (`\u002Fv1`).\n3. Browser opens websocket matchmaking to `match-coordinator` (`\u002Fqueue`) to enter duels.\n4. `match-coordinator` assigns a match + gameplay route and issues ticket.\n5. Browser upgrades to websocket through `realtime-gateway` (`\u002Fws\u002F{node}`), which proxies to the assigned `gameplay-node`.\n6. `gameplay-node` runs duel engine and broadcasts authoritative snapshots.\n\n### Match route flow\n\n- `\u002F` is the lobby and launcher.\n- `\u002Fmatch\u002F[id]` is the canonical route for a specific match.\n- Cold loads resolve through `GET \u002Fv1\u002Fmatches\u002F{id}\u002Fbootstrap`.\n- Already-authenticated route refreshes can resolve through `GET \u002Fv1\u002Fmatches\u002F{id}\u002Fsession`.\n- A match route can resolve to:\n  - live reconnect with a minted gameplay ticket\n  - saved history \u002F end-of-match snapshot\n  - replaced, forbidden, or missing state\n\n### Kubernetes ingress routing (prod)\n\n- `\u002F` -> `web`\n- `\u002Fv1` -> `api`\n- `\u002Fqueue` and `\u002Fqueue\u002Fonline` -> `match-coordinator`\n- `\u002Fws` -> `realtime-gateway`\n\n## Container images\n\nDevelopment (`docker-compose.yml`) uses language runtime images for fast iteration on the backend:\n\n- `golang:1.26` for `api`, `match-coordinator`, `gameplay-node`\n- `postgres:16`\n- `redis:7`\n\nThe web app is typically run directly from `apps\u002Fweb` with Node during local development.\n\nProduction images are built from service Dockerfiles and pushed to registry:\n\n- `geoduels-api`\n- `geoduels-match-coordinator`\n- `geoduels-realtime-gateway`\n- `geoduels-gameplay-node`\n- `geoduels-web`\n- `geoduels-location-ingest`\n\n## Maintenance and draining\n\n- `gameplay-node` marks itself draining on shutdown, refuses new match creation, and waits for active matches to finish before exit.\n- `match-coordinator` excludes draining gameplay nodes from new duel assignment.\n- `realtime-gateway` stops accepting new websocket upgrades during shutdown and waits for active proxied sockets to close.\n- `api`, `match-coordinator`, `realtime-gateway`, and `gameplay-node` all fail readiness while draining so Kubernetes can stop routing new traffic.\n- Redis key `system:maintenance` can publish lobby maintenance state:\n  - `queuePaused`: pause duel queueing\n  - `playPaused`: pause all new play sessions\n  - `phase: warning|active`: drive lobby warning banner \u002F blocking maintenance overlay\n\n## Local development\n\nPrerequisites:\n\n- Docker Desktop\n- Go 1.26+\n- Node 20+\n\nStart:\n\n```bash\ncp .env.example .env\ncp apps\u002Fweb\u002F.env.local.example apps\u002Fweb\u002F.env.local\ndocker compose up -d postgres redis\n.\u002Fscripts\u002Fmigrate.sh up\nPOSTGRES_URL='postgres:\u002F\u002Fgeoduels:geoduels@127.0.0.1:5432\u002Fgeoduels?sslmode=disable' \\\n  go run .\u002Fworkers\u002Flocation-ingest \\\n  -dataset datasets\u002Fa-source-world.sample.json \\\n  -map-key a-source-world\ndocker compose up -d gameplay-node match-coordinator realtime-gateway api\ncd apps\u002Fweb && npm ci && npm run dev\n```\n\nThe tracked sample map at `datasets\u002Fa-source-world.sample.json` contains 10 public landmark locations so contributors can launch a playable local stack without private location data. To use a larger local dataset, keep it in ignored `datasets\u002F*.json` and pass that path to `workers\u002Flocation-ingest`.\n\nEndpoints:\n\n- Web: `http:\u002F\u002Flocalhost:3000`\n- API health: `http:\u002F\u002Flocalhost:8080\u002Fhealth`\n- Queue health: `http:\u002F\u002Flocalhost:8090\u002Fhealth`\n- Gameplay health: `http:\u002F\u002Flocalhost:8091\u002Fhealth`\n\nStop:\n\n```bash\ndocker compose down\n```\n\n## CI\u002FCD\n\n### Production release (`.github\u002Fworkflows\u002Frelease-prod.yml`)\n\nTriggered by git tag push.\n\n- Run Go, web, and manifest checks\n- Build and push versioned production images\n- Open a PR against the private ops repository configured by `OPS_REPOSITORY`\n- Update production image tags and `NEXT_PUBLIC_APP_VERSION` in that ops repository\n- Deploy after that release PR is merged and Flux reconciles production\n\n## Production checklist\n\n1. Provision k3s cluster, ingress, DNS, and TLS.\n2. Create namespace and required secrets (`geoduels-secrets`, `ghcr-creds`) in the private ops flow.\n3. Apply DB migrations in `db\u002Fmigrations`.\n4. Configure the release workflow variables\u002Fsecrets, especially `OPS_REPO_TOKEN`.\n5. Push a release tag (for example `v1.2.3`) to build images and open the Flux release PR.\n6. Merge the generated release PR to trigger production rollout through Flux.\n7. Run post-deploy health checks for `\u002Fhealth`, queue flow, and websocket gameplay.\n\n## Repo pointers\n\n- `docker-compose.yml` - local stack\n- `infra\u002Fk3s\u002Fbase` - base k8s manifests\n- `infra\u002Fk3s\u002Foverlays\u002Fk3d` - local 3-node k3d overlay for routing\u002Fscaling tests\n- production overlays and Flux cluster state live in the private ops repository\n- `services\u002F*\u002FDockerfile`, `apps\u002Fweb\u002FDockerfile`, `workers\u002Flocation-ingest\u002FDockerfile` - production image definitions\n- `docs\u002Farchitecture.md` - current runtime architecture\n","GeoDuels 是一个免费的地理猜测游戏平台，支持对战、排名、自定义房间以及作弊缓解措施。项目采用TypeScript编写前端，并利用Go语言构建后端服务，通过Kubernetes实现水平扩展，确保高可用性和性能。核心功能包括基于WebSocket的实时匹配系统、权威的游戏状态广播机制和地理位置数据集的验证与导入。它适合用于教育场景中的地理学习、团队建设活动或任何希望以趣味方式增进地理知识的应用场合。",2,"2026-06-11 04:04:52","CREATED_QUERY"]