尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Dinder — 实时群组餐厅决策。滑动、匹配、就餐:基于 Socket.IO 的房间通过 Redis,30 分钟 TTL 会话和基于 SINTER 的共识。
AI 翻译
Dinder — real-time group restaurant decisions. Swipe, match, eat: Socket.IO rooms over Redis with 30-minute TTL sessions and SINTER-based consensus.
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
What are we doing tonight? YupCrew helps friends, couples and housemates choose somewhere to eat, takeaway, a recipe to cook, or a movie or series to watch. Invite your people with a link, choose together and swipe to find a shared pick. No download or account needed.
Formerly Dinder. Public address: yupcrew.com. Existing Dinder addresses remain available for current Sessions and Shopping Lists; internal service and storage identifiers are unchanged.查看完整 README ↗What are we doing tonight? YupCrew helps friends, couples and housemates choose somewhere to eat, takeaway, a recipe to cook, or a movie or series to watch. Invite your people with a link, choose together and swipe to find a shared pick. No download or account needed.
Formerly Dinder. Public address: yupcrew.com. Existing Dinder addresses remain available for current Sessions and Shopping Lists; internal service and storage identifiers are unchanged.
SINTER when the last person submits.session:expired so clients aren't left polling a dead session.sequenceDiagram
participant H as Host
participant P as Participant
participant S as Backend
participant R as Redis
H->>S: POST /api/sessions
S->>R: create session keys (30-min TTL)
S-->>H: session code
P->>S: session:join
S->>R: add participant, refresh TTL (Lua)
S-->>H: participant:joined
H->>S: selection:submit (liked place IDs)
S->>R: SADD likes, refresh TTL (Lua)
S-->>P: participant:submitted
P->>S: selection:submit
S->>R: SINTER across participant sets
S-->>H: session:results (matches)
S-->>P: session:results (matches)
Note over S,R: if 30 min pass, keys expire — keyspace notification → session:expired
The full client/server event contract — session:join, selection:submit, session:restart, session:leave inbound; participant:joined/submitted/left/disconnected, session:results, session:restarted, session:expired outbound — is typed once in shared/types/websocket-events.ts and imported by both sides, so the frontend and backend cannot drift apart silently.
npm workspaces monorepo, three packages:
| Package | What it is |
|---|---|
backend/ | Node 22 + TypeScript, Express 4, Socket.IO 4.7, ioredis, Zod validation. One handler file per socket event, services for sessions, friends, and restaurant search. |
frontend/ | React 18 + Vite, Tailwind, Zustand for state, socket.io-client. Mobile-first. |
shared/ | @dinder/shared — the typed WebSocket event contract and Zod schemas both sides import. |
The Redis data model is the app. A session is a handful of keys (session:<code>, session:<code>:participants, session:<code>:<participantId>:selections, …), all carrying the same 30-minute TTL. Consensus is SINTER over the per-participant selection sets, and TTL refresh happens in one atomic inline Lua script so a session's keys can never expire out of sync — all in sessionStore.ts. sessionExpiryNotifier.ts subscribes to keyspace expired events to tell connected clients the moment a session dies.
REST is deliberately thin — POST /api/sessions, GET /api/sessions/:code — everything live goes over the socket.
Prerequisites: Node 22, Docker (for Redis), and a Google Places API key.
git clone https://github.com/Zacplischka/dinner_app.git
cd dinner_app
npm install
# Redis
docker run -d -p 6379:6379 redis:7-alpine
# the backend reads backend/.env
echo "GOOGLE_PLACES_API_KEY=your-key-here" > backend/.env
# backend on http://localhost:3001, frontend on http://localhost:3000
npm run dev
| Variable | Needed for |
|---|---|
GOOGLE_PLACES_API_KEY | Restaurant search for Eat Out and Takeaway Sessions |
SPOONACULAR_API_KEY | Sourced Recipes for the Cook Branch |
PORT, REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, FRONTEND_URL | All have local defaults |
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY | Optional Google sign-in / friends feature only |
VITE_BACKEND_URL | Frontend → backend (REST under /api, and the socket); defaults to the local backend on port 3001 |
VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY | Frontend sign-in; without them auth is disabled with a console warning |
Everything runs from the repo root. shared/ builds first — backend and frontend import @dinder/shared as a file dependency — and build and typecheck take care of that.
npm run dev # backend + frontend together
npm run build # shared → backend → frontend, in that order
npm test # backend + frontend vitest suites (the backend's include contract + integration, which need Redis)
npm run typecheck # builds shared, then tsc --noEmit on both
npm run lint # eslint both workspaces
npm run format # prettier both workspaces
npm run gen:types # regenerate supabase/database.types.ts
Narrower runs go through the workspace: npm run test:unit --workspace=backend, npm run test:contract --workspace=backend (Redis required), npm run test:e2e --workspace=frontend (Playwright; mobile-chrome is the primary project). The WebSocket contract is typed once in shared/types/websocket-events.ts and the contract tests assert the backend against it — they are the source of truth for the realtime protocol.
CI (ci-cd.yml) runs typecheck, lint and both unit suites in one job and the contract suite against a Redis service in another; a green main auto-deploys.
Both services deploy to Railway: the backend via Railpack (npm ci && npm run build && npm run start from the repo root), the frontend as a static SPA.
CONTEXT.md defines domain language; ADRs record architecture decisions.
MIT © Zac Plischka