{"id":50964737,"url":"https://github.com/borland667/prode","last_synced_at":"2026-06-18T18:32:39.333Z","repository":{"id":351450358,"uuid":"1210867651","full_name":"borland667/prode","owner":"borland667","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-09T15:19:18.000Z","size":878,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-09T16:13:09.673Z","etag":null,"topics":["classic","game","prode"],"latest_commit_sha":null,"homepage":"https://prode.grupoeglu.net","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/borland667.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-14T20:50:48.000Z","updated_at":"2026-06-09T15:39:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/borland667/prode","commit_stats":null,"previous_names":["borland667/prode"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/borland667/prode","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borland667%2Fprode","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borland667%2Fprode/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borland667%2Fprode/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borland667%2Fprode/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/borland667","download_url":"https://codeload.github.com/borland667/prode/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/borland667%2Fprode/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34503508,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["classic","game","prode"],"created_at":"2026-06-18T18:32:37.922Z","updated_at":"2026-06-18T18:32:39.325Z","avatar_url":"https://github.com/borland667.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Prode\n\nProde is a multilingual sports prediction app centered on Prode-style tournament picks (English, Spanish, Portuguese, Italian, and Dutch UI). The current product is production-shaped around football tournaments that have:\n\n- a group stage\n- a knockout bracket\n- optional best-third-place qualification slots\n- tournament-wide public or private access\n- optional prize configuration\n- private leagues inside a tournament\n\nThe app currently supports one prediction engine: football group-stage plus knockout Prode. The product already uses generic tournament, mode, and sport metadata, but non-football formats such as NBA playoff series are not implemented yet.\n\n## Current Implementation Snapshot\n\nImplemented today:\n\n- public and private tournaments with join codes\n- tournament-scoped prediction flow for the main tournament entry\n- private leagues inside a tournament, each with its own prediction scope\n- copy an existing tournament or league prediction set into a private league scope\n- official primary-entry selection per user per tournament\n- tournament leaderboard based on the tournament scope only\n- league leaderboard based on the corresponding league scope only\n- shared leaderboard hub that can switch across accessible tournaments and private league boards\n- authenticated global rankings based on each user’s official primary entry and opt-in visibility setting\n- registration, login, logout, Google OAuth, forgot-password, reset-password, and profile management\n- browser language detection with English fallback (uses `navigator.languages` order among supported UI languages)\n- regional date and number formatting from the browser locale when it matches the active UI language (for example `es-MX`, `pt-PT`, `en-GB`), with regional defaults otherwise\n- dark and light themes with persisted preference\n- World Cup 2026 support including best-third-place Round of 32 handling\n- seeded football tournament catalog beyond World Cup 2026\n- admin tournament builder, safe structure editing, tournament settings, results entry, and score recalculation\n- design-system-based UI is now the default across the app, while legacy alias classes remain mapped for compatibility\n\nImportant current product rules:\n\n- the tournament page represents the tournament-wide official competition\n- users can also make separate predictions inside multiple private leagues\n- only one scope can count as the user’s official tournament entry at a time\n- global rankings only include logged-in users who have not opted out of visibility\n- the current global ranking uses official entries so multiple league entries do not inflate a user’s score\n\n## Seeded Tournament Catalog\n\nThe local seed currently installs:\n\n- FIFA World Cup 2026\n- UEFA Euro\n- Copa América\n- AFC Asian Cup\n- Africa Cup of Nations\n\nNotes:\n\n- World Cup 2026 is the most complete seed and includes explicit best-third-place bracket handling.\n- The other seeded tournaments are curated, format-compatible football templates that fit the current engine. They are not live official imports.\n\n## Tech Stack\n\n| Layer | Technology |\n| --- | --- |\n| Frontend | React 19, React Router 7, Vite 8 |\n| Styling | Tailwind CSS v4 via Vite plus semantic design-system classes in `src/index.css` |\n| Backend | Express 5 |\n| Database | PostgreSQL |\n| ORM | Prisma 7 |\n| Auth | JWT + Passport Google OAuth |\n| Email | Nodemailer |\n| Deploy Shape | SPA + API, Netlify-compatible |\n\n## Documentation Map\n\n- [README.md](./README.md): local setup, current features, commands, and operational notes\n- [docs/IMPLEMENTATION_STATUS.md](./docs/IMPLEMENTATION_STATUS.md): where the app stands today, what is working, and what is next\n- [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md): routes, backend responsibilities, schema model, prediction scopes, and API surface\n- [docs/QA_CHECKLIST.md](./docs/QA_CHECKLIST.md): manual QA and smoke-test checklist\n- [docs/ROADMAP.md](./docs/ROADMAP.md): prioritized next steps after the current implementation snapshot\n- [docs/DESIGN_SYSTEM.md](./docs/DESIGN_SYSTEM.md): UI primitives, styling rules, and migration guidance\n- [docs/KEYCLOAK_MIGRATION.md](./docs/KEYCLOAK_MIGRATION.md): future auth portability path\n- [AGENTS.md](./AGENTS.md): repository working standards and implementation expectations\n\n## Local Development\n\n### Prerequisites\n\n- Node.js `24.1.0`\n- `nvm` recommended\n- Docker\n- PostgreSQL if not using Docker\n\nThe repo includes `.nvmrc`, so the recommended setup is:\n\n```bash\nnvm install\nnvm use\n```\n\n### Install Dependencies\n\n```bash\nnpm install\n```\n\n### Configure Environment\n\n```bash\ncp .env.example .env\n```\n\nRecommended local values:\n\n```env\nDATABASE_URL=postgresql://prode:prode123@localhost:5432/prode\nTEST_DATABASE_URL=postgresql://prode:prode123@127.0.0.1:5432/prode\nJWT_SECRET=local-dev-secret-change-me\n\nGOOGLE_CLIENT_ID=\nGOOGLE_CLIENT_SECRET=\nGOOGLE_CALLBACK_URL=http://localhost:3001/api/auth/google/callback\n\n# Frontend Google OAuth (optional)\n# Set to your Google Client ID to enable the Google login button on the frontend.\n# This is separate from GOOGLE_CLIENT_SECRET to prevent exposing secrets in the browser.\n# If not set, the Google login button will not appear.\nVITE_GOOGLE_CLIENT_ID=\n\nSITE_URL=http://localhost:5173\n\nEMAIL_FROM_ADDRESS=noreply@example.com\nEMAIL_FROM_NAME=Prode\nSMTP_HOST=localhost\nSMTP_PORT=1025\nSMTP_SECURE=false\nSMTP_USER=\nSMTP_PASSWORD=\n\nVITE_ANALYTICS_ENABLED=false\nVITE_ANALYTICS_PROVIDER=none\nVITE_POSTHOG_KEY=\nVITE_POSTHOG_HOST=https://us.i.posthog.com\n```\n\nNotes:\n\n- Google OAuth is optional in local development.\n- Google OAuth is only enabled when `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `GOOGLE_CALLBACK_URL` are all set together (backend).\n- The frontend Google login button only appears when `VITE_GOOGLE_CLIENT_ID` is set.\n- In production, `GOOGLE_CALLBACK_URL` should normally be `${SITE_URL}/api/auth/google/callback`, and that exact URI must be registered in Google Cloud OAuth settings.\n- SMTP is optional in local development. Without SMTP, email/password registration falls back to the legacy immediate-login flow, and the forgot-password flow returns a local reset URL/token payload for testing.\n- Frontend analytics is optional. Set `VITE_ANALYTICS_ENABLED=true`, `VITE_ANALYTICS_PROVIDER=posthog`, and `VITE_POSTHOG_KEY` to enable PostHog; otherwise the shared analytics adapter stays disabled.\n\n### Google OAuth Configuration\n\nTo enable Google OAuth login:\n\n1. **Backend Configuration** (required for all Google OAuth flows):\n   - Create OAuth credentials in [Google Cloud Console](https://console.cloud.google.com/)\n   - Set `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `GOOGLE_CALLBACK_URL`\n   - All three must be set for Google OAuth to work\n   - The callback URL must match exactly what's configured in Google Cloud\n\n2. **Frontend Configuration** (optional, only needed to show the login button):\n   - Set `VITE_GOOGLE_CLIENT_ID` to your Google Client ID\n   - This exposes only the Client ID (not secret) to the browser\n   - If not set, the Google login button will be hidden\n\n3. **Production Deployment**:\n   - In production, `GOOGLE_CALLBACK_URL` should be `${SITE_URL}/api/auth/google/callback`\n   - Register this exact URL in your Google Cloud OAuth credentials\n   - Ensure `VITE_GOOGLE_CLIENT_ID` matches the production Client ID\n\nFor development, you can omit Google OAuth configuration entirely - the app will still work with email/password authentication.\n\n### Run PostgreSQL With Docker\n\n```bash\ndocker run -d \\\n  --name prode-postgres \\\n  -e POSTGRES_USER=prode \\\n  -e POSTGRES_PASSWORD=prode123 \\\n  -e POSTGRES_DB=prode \\\n  -p 5432:5432 \\\n  postgres:16-alpine\n```\n\nUseful Docker commands:\n\n```bash\ndocker ps\ndocker stop prode-postgres\ndocker start prode-postgres\n```\n\n### Apply Migrations\n\nUse checked-in migrations for schema changes:\n\n```bash\nnpm run db:migrate\n```\n\nGenerate the Prisma client if needed:\n\n```bash\nnpm run db:generate\n```\n\n### Seed Data\n\n```bash\nnpm run db:seed\nnpm run db:backfill:translations\n```\n\n`db:backfill:translations` is safe to run after seeding or against an existing local database. It fills missing Spanish names for tournaments, rounds, modes, and teams without requiring a full reseed.\n\n### Start The App\n\n```bash\nnpm run dev\n```\n\nLocal URLs:\n\n- frontend: `http://localhost:5173`\n- API: `http://localhost:3001`\n- health check: `http://localhost:3001/api/health`\n\n`npm run dev` uses `scripts/dev.cjs` to supervise the API and Vite processes so local shutdown works cleanly with `Ctrl+C`.\n\n## Local Database Reset\n\nIf you want a clean local database while keeping the container:\n\n```bash\nnpx prisma migrate reset\n```\n\nIf you want to skip the automatic seed and reseed manually:\n\n```bash\nnpx prisma migrate reset --skip-seed\nnpm run db:seed\nnpm run db:backfill:translations\n```\n\nIf you want to fully recreate the Docker database container:\n\n```bash\ndocker rm -f prode-postgres\n```\n\nThen rerun the `docker run ... postgres:16-alpine` command above.\n\nImportant:\n\n- prefer checked-in migrations over `db push`\n- do not overwrite a shared or remote database when resetting locally\n- use `TEST_DATABASE_URL` for automated tests so test setup does not target the wrong database\n\n## Commands\n\n| Command | Description |\n| --- | --- |\n| `npm run dev` | Start frontend and API together |\n| `npm run dev:web` | Start only the Vite frontend |\n| `npm run dev:api` | Start only the API server |\n| `npm run build` | Production frontend build |\n| `npm run lint` | ESLint |\n| `npm test` | Node test suite, including API integration and utility tests |\n| `npm run verify` | Lint, Prisma generate, Prisma validate, test suite, production build |\n| `npm run db:generate` | Generate Prisma client |\n| `npm run db:validate` | Validate Prisma schema |\n| `npm run db:migrate` | Create/apply a local development migration |\n| `npm run db:migrate:deploy` | Apply checked-in migrations only |\n| `npm run db:migrate:status` | Show migration status |\n| `npm run db:push` | Push schema without migrations, use sparingly |\n| `npm run db:seed` | Seed the football tournament catalog |\n| `npm run db:backfill:translations` | Fill missing translated names in existing rows |\n| `npm run db:studio` | Open Prisma Studio |\n| `npm run import:results` | Pull results from football-data.org via the admin API (requires `RESULTS_IMPORT_*` env, see below) |\n\n## Implemented User Flows\n\n### Guest Flows\n\n- browse the landing page\n- inspect the featured tournament\n- inspect active tournaments\n- open tournament detail pages\n- switch language and theme\n\n### Authenticated User Flows\n\n- register with email/password\n- log in with email/password\n- log in with Google when configured\n- request a password reset\n- reset password with a one-time token\n- update display name\n- update avatar URL\n- change password\n- opt in or out of global ranking visibility\n\n### Tournament Participation\n\n- join a private tournament with a join code\n- submit tournament-scope predictions\n- revisit and update tournament-scope predictions while the tournament is still open\n- clear predictions through the API for any still-open matches using `DELETE /api/predictions/:id`\n- use random-fill from the prediction flow to generate a valid bracket across all steps\n- view group standings and knockout progress once results exist\n\n### League Participation\n\n- create a private league inside a tournament\n- join a league using a join code\n- share league invite links\n- submit league-scope predictions\n- copy an existing tournament or league prediction set into a private league scope\n- choose a league scope as the official primary entry if it has predictions\n- leave a league\n- delete a league if you are the owner\n\n### Rankings\n\n- tournament leaderboard for the tournament-wide official scope\n- league leaderboard for league members only\n- leaderboard hub that switches between tournament boards and accessible private league boards\n- global rankings across official entries for visible users only\n\n### Admin\n\n- create tournaments from JSON group/round structures\n- edit tournament structure only when the tournament is still structurally safe to change\n- update tournament settings:\n  - access type\n  - join code\n  - prize enable/disable\n  - entry fee\n  - currency\n- enter group results\n- enter knockout results\n- recalculate scores manually\n- pull results from football-data.org when the server has `RESULTS_IMPORT_API_KEY` configured (see \"Results Importer\" below)\n\n## Results Importer\n\n`POST /api/tournaments/:id/import-results` (admin) calls football-data.org and applies new results without overwriting existing ones.\n\n- Server-side: set `RESULTS_IMPORT_API_KEY` (free API key from football-data.org). When unset the endpoint returns 503.\n- Client-side: `npm run import:results` runs `scripts/import-results.cjs`, which logs in as the admin user given by `RESULTS_IMPORT_ADMIN_EMAIL` / `RESULTS_IMPORT_ADMIN_PASSWORD` and POSTs the import endpoint against `RESULTS_IMPORT_API_BASE_URL`.\n- GitHub Actions: `.github/workflows/import-results.yml` runs the same CLI on a 6-hour schedule and also via `workflow_dispatch`. The workflow is env-gated; without the secrets the script logs the missing variables and exits 0.\n- Semantics: group standings upsert only when no `GroupResult` exists for that group, and knockout matches update only when the DB row has both selected team IDs populated and `status != 'finished'`. Admin-entered data is never overwritten.\n\n## Current Scoring And Prediction Model\n\nGroup-stage scoring:\n\n- 4 points: both teams correct in correct order\n- 3 points: both teams correct in inverted order\n- 2 points: one team correct in correct position\n- 1 point: one team correct in wrong position\n- 0 points: no correct teams\n\nKnockout scoring:\n\n- stored on each `Round` as `pointsPerCorrect`\n- seeded tournaments currently use a linear scaling pattern through the bracket\n- rules display and maximum score are derived from the tournament mode plus round configuration\n\nWorld Cup 2026:\n\n- supports best-third-place slot assignment in the expanded 48-team bracket\n- users explicitly place eligible third-placed teams where required by the prediction flow\n\n## Testing And Verification\n\nCurrent automated coverage includes:\n\n- scoring logic\n- tournament utility logic\n- translation helper logic\n- email helper logic\n- end-to-end API integration coverage for core auth, tournament, league, and leaderboard flows\n- production build validation\n\nCI:\n\n- GitHub Actions runs `npm run verify`\n- CI provisions PostgreSQL for the test suite\n- on pushes to `main`, GitHub Actions also runs `npm run db:migrate:deploy` for production when the `PRODUCTION_DATABASE_URL` GitHub secret is configured\n- if `PRODUCTION_DATABASE_URL` is not set, the `migrate-production` job is skipped (a prior gate job reads it from `env` and exposes a boolean output) without failing CI\n\n## Google OAuth Testing\n\nTo test Google OAuth locally:\n\n1. Set up Google Cloud OAuth credentials\n2. Configure all three backend variables: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_CALLBACK_URL`\n3. Set `VITE_GOOGLE_CLIENT_ID` to enable the frontend login button\n4. Run `npm run dev` and test the Google login flow\n5. Test both successful login and failure scenarios\n\nNote: The Google login button will not appear unless `VITE_GOOGLE_CLIENT_ID` is set, even if backend OAuth is configured.\n\n## Deployment And Production Migrations\n\nNetlify remains the production host shape for the SPA and serverless API. Production database migrations are applied from GitHub Actions rather than from the Netlify build itself.\n\nCurrent production deployment expectation:\n\n- the Netlify function that wraps Express is `prode-http` (see `netlify.toml`): it cannot be named `api` because that collides with the repository `api/` directory in the serverless bundle and breaks Node ESM resolution\n- pushing to `main` triggers `.github/workflows/ci.yml`\n- the `verify` job must pass first\n- after `verify`, on pushes to `main`, `check-production-migrate` sets `run_migrate` from whether `PRODUCTION_DATABASE_URL` is non-empty (secret injected into step `env`, never logged)\n- the `migrate-production` job runs only when that output is `true`, then runs `npm run db:migrate:deploy`\n- if the secret is absent, `migrate-production` is skipped and the workflow still completes successfully\n\nRequired GitHub secret:\n\n- `PRODUCTION_DATABASE_URL`: the production PostgreSQL connection string Prisma should use for `migrate deploy`\n\nOperational notes:\n\n- keep production schema changes migration-first and checked into `prisma/migrations`\n- do not rely on `db push` in production\n- GitHub Actions and Netlify are separate systems, so application changes should stay forward-compatible with normal deploy timing\n\n## Current Boundaries\n\nThe app is not yet:\n\n- a generic multi-sport engine for non-football tournament formats\n- integrated with official live competition feeds\n- integrated with payment providers or automated payouts\n- fully stripped of every legacy alias class, even though the design system is now the default UI layer\n- covered by browser-level E2E tests\n\nFor the current implementation snapshot and the prioritized next steps, see:\n\n- [docs/IMPLEMENTATION_STATUS.md](./docs/IMPLEMENTATION_STATUS.md)\n- [docs/ROADMAP.md](./docs/ROADMAP.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborland667%2Fprode","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fborland667%2Fprode","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fborland667%2Fprode/lists"}