CoOp:Anything
게임을 검색해 협동 플레이 가능한 모드·단계·품질·커뮤니티 평점을 확인합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Aplicação web para gestão de torneios de Beyblade X: cadastro de jogadores e beyblades, torneios (Suíço/mata-mata/grupos), placar ao vivo nas arenas (telão), painel do juiz, estatísticas, tiers e comunidade.
Documentação de manutenção. Mantenha este arquivo atualizado ao mexer no app.
| Camada | Tecnologia |
|---|---|
| Framework | Next.js 14 (App Router, Server + Client Components), TypeScript |
| UI | React 18, Tailwind CSS |
| Banco | PostgreSQL (Supabase, via pooler) |
| ORM | Prisma 5 |
| Auth | NextAuth v4 (estratégia JWT, sem sessão em banco) |
| Nodemailer (SMTP) — verificação de conta e reset de senha | |
| Deploy | Vercel (atual) |
| Tempo real | Polling adaptativo + WebRTC P2P (LAN) + Service Worker (offline) |
npm install # instala deps (roda prisma generate no postinstall)
cp .env.example .env # crie o .env (veja variáveis abaixo)
npm run dev # http://localhost:3000
Scripts:
| Script | O que faz |
|---|---|
npm run dev | Servidor de desenvolvimento |
npm run build | prisma generate + build de produção |
npm run start | Sobe o build de produção (next start) |
npm run lint | ESLint |
npm run db:generate | Regenera o Prisma Client |
npm run db:push | Aplica o schema no banco (dev) |
Antes de commitar mudanças que possam quebrar o build, sempre rode
npx tsc --noEmit e npm run build.
| Variável | Obrigatória | Descrição |
|---|---|---|
DATABASE_URL | ✅ | String de conexão do Postgres (pooler do Supabase) |
NEXTAUTH_SECRET | ✅ | Segredo do NextAuth (JWT) |
NEXTAUTH_URL | ✅ | URL pública do app (ex.: https://seuapp.vercel.app) |
DB_CONNECTION_LIMIT | ⛔ opcional | Limite de conexões do Prisma |
DB_POOL_TIMEOUT | ⛔ opcional | Timeout do pool |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM / SMTP_SECURE | para e-mail | Envio de verificação de conta e reset de senha |
O schema fica em prisma/schema.prisma. A aplicação de mudanças em produção
NÃO usa prisma migrate — usa SQL idempotente na rota GET/POST /api/migrate
(src/app/api/migrate/route.ts), que roda ADD COLUMN IF NOT EXISTS,
CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, etc.
Fluxo ao alterar o schema:
prisma/schema.prisma.migrations em /api/migrate.npx prisma generate (ou npm run db:generate)./api/migrate uma vez para aplicar no banco.As tabelas Match, MatchPoint, MatchSet e TournamentParticipant têm
índices declarados no schema e criados via /api/migrate
(Match_tournamentId_round_idx, MatchPoint_matchId_idx, etc.). Sem eles, os
endpoints de polling do placar fazem varredura completa das tabelas e o app fica
lento. Sempre rode /api/migrate após o deploy.
src/
├── app/
│ ├── arena/ArenaDisplay.tsx # TELÃO da arena (placar ao vivo, vídeos)
│ ├── tournaments/[id]/
│ │ ├── page.tsx # Página do torneio (auto-cura do suíço no load)
│ │ ├── ScoreModal.tsx # PAINEL DO JUIZ (marcar pontos, iniciar batalha)
│ │ └── AdminMatchEditor.tsx # Ferramentas de admin (editar placar, avançar, etc.)
│ └── api/ # Rotas de API (ver abaixo)
├── lib/
│ ├── tournament-engine.ts # Motor dos torneios (suíço, byes, mata-mata, standings)
│ ├── arena-schedule.ts # Distribui partidas por arena + juízes
│ ├── arenaLink.ts # WebRTC P2P (juiz ↔ telão na LAN)
│ ├── arenaLayout.ts # Layout/posições dos elementos do telão
│ ├── scoring.ts # Pontuação por tipo de finish
│ ├── tiers.ts / meta-tiers.ts # Tiers dos bladers / meta das peças
│ ├── auth.ts # Config do NextAuth
│ └── prisma.ts # Instância do Prisma
└── components/
├── ServiceWorkerRegister.tsx # Registra o /sw.js
└── TierBadge.tsx
public/
├── sw.js # Service worker (offline)
├── countdown.mp4 # Vídeo de contagem/lançamento
└── finish-videos/*.mp4 # Vídeos por tipo de finish
/arena)[email protected] … [email protected], senha padrão
Arena123456, criados por /api/seed-arenas). O número da arena vem do e-mail./arena?n=1.ArenaLayout via /api/arena-layout./api/arena (placar completo) e /api/arena/tick (sinais leves de
contagem/finish/lançamento) são consultados pelo telão.ArenaDisplay.tsx.ScoreModal.tsx)ref (não por estado) em addPoint,
startBattle, playLaunchVideo — evita ponto/vídeo em dobro./countdown.mp4) e finishes (/finish-videos/*.mp4) tocam no telão.ArenaDisplay.tsx): contagem/lançamento 8s,
finish 6s — impede replay quando o mesmo evento chega pelo P2P e pelo poll.preload="metadata" (os vídeos são "preparados" no gesto de iniciar o telão,
não no load).Três camadas (ver conversa de manutenção):
public/sw.js, cache lbl-cache-v2): cache-first para
página/mídia, network-only para /api/* (dados nunca ficam velhos).
Precache só de assets leves; vídeos são cacheados no primeiro uso.arenaLink.ts + /api/arena/rtc): juiz e telão na mesma
rede conectam direto; contagem/finish/lançamento/placar chegam instantâneos
e seguem funcionando se a internet oscilar. Sinalização (troca de SDP) via o
servidor; depois flui P2P. O telão faz backoff na busca por par (4s ativo
→ 30s quando não há juiz na LAN → 15s conectado).Limite: é continuidade de exibição. O ponto só é gravado no banco quando a conexão volta (o P2P mantém o placar certo na tela enquanto isso).
tournament-engine.ts)ROUND_ROBIN. Nº de rodadas = ceil(log2(participantes))
(swissRoundCount). O mata-mata começa na rodada swissRounds + 1 — esse é
o limite usado em todo o motor (avanço e ranking final).scoring.ts): Spin 1 · Over 2 · Burst 2 · Extreme 3 ·
Misslaunch 1. O set fecha quando alguém atinge pointsToWinSet.| Campo | Significado | Alimenta |
|---|---|---|
totalPoints | Suíço: vitórias + byes. Outros formatos: soma dos pontos de batalha | Classificação do torneio |
wins / losses | Só partidas realmente jogadas (bye NÃO conta) | Tiers, win-rate, ranking global, estatísticas de carreira |
totalPoints → pontos de
batalha marcados → saldo (marcados − sofridos).isWalkover), que soma no totalPoints para ele não ser penalizado por
folgar — mas não entra em wins (não é vitória de carreira). Rodízio justo:
ninguém recebe 2 byes antes de todos receberem 1.generateSwissRound) — validadas por simulação
(25/24/13/9/7 jogadores, 200 execuções cada):
finalizeTournamentRanking): define placement e distribui
rankingPoints ao top 5 (100/70/50/30/10). O /rankings global soma os
dos torneios .| Botão | Rota | O que faz |
|---|---|---|
| Editar Placar | /api/admin/matches/[id]/set-score | Corrige pontos direto (sem resetar, sem tocar nas arenas) |
| Resetar | /api/admin/matches/[id]/reset | Zera a partida para reinserir pelo placar |
| Avançar Rodada | /api/admin/tournaments/[id]/advance-round | Gera a próxima rodada se travou |
| Remover Duplicadas | /api/admin/tournaments/[id]/dedupe-matches | Remove partidas duplicadas |
| Recalcular Classificação | /api/admin/tournaments/[id]/recalc-standings | Recomputa V/pontos de todos |
| Recalcular Ranking Final | /api/admin/tournaments/[id]/recalc-ranking | Refaz colocação + pontos de ranking (funciona em torneio já encerrado) |
| Verificar Partidas | /api/admin/tournaments/[id]/balance-matches | Confere se todos jogaram o mesmo nº e gera partidas de reposição (refaz o mata-mata se preciso) |
auth/[...nextauth], auth/register, auth/verify-email,
auth/forgot-password, auth/reset-password.tournaments, tournaments/[id] (+ join, start, finish,
reset, participants, finals-deck).matches/[id]/point, undo-point, countdown,
launch-video, onair, sides, deck-order, sets, wo.arena, arena/tick, arena/rtc, arena-layout.admin/tournaments/[id]/*, admin/matches/[id]/*, admin/users,
admin/beyparts, admin/announcements, etc.migrate (migrações), seed-arenas, seed-test-users.main/branch → build automático. npm run build roda prisma generate./api/migrate./api/arena roda os resolvers independentes em paralelo (Promise.all) e
cacheia imagens de BeyPart por instância (120s).<img> cru (sem next/image); public/liga.png é grande (~5 MB)
e só usada no certificado — candidata a otimização futura.onair) e se /api/migrate foi rodado./api/migrate criou os índices.rankingPointsadvanceSwissTournament / ensureSwissProgress gera a
próxima rodada ou o mata-mata quando a rodada atual termina. A página do
torneio chama ensureSwissProgress no load (com throttle de 15s) para
auto-curar casos em que a última partida fechou por um caminho que não avançou.generateSwissRound é idempotente (não duplica rodada).generatePlayoffBracket (top-N) + advanceSingleElimination.