# 03 — Arquitetura do Sistema ## Diagrama de Arquitetura ``` ┌─────────────────────────────────────────────────────────────┐ │ FIREBASE │ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │ │ │ Firestore │ │ Auth │ │ Hosting │ │ │ │ (Database) │ │ (Admin only)│ │ (Deploy) │ │ │ └──────┬──────┘ └──────────────┘ └───────────────┘ │ │ │ │ └──────────┼────────────────────────────────────────────────── ┘ │ Real-time listeners ┌──────┴──────────────────────────┐ │ │ ▼ ▼ ┌──────────────────┐ ┌──────────────────────┐ │ ADMIN DASHBOARD │ │ APLICAÇÃO CLIENTE │ │ (Este projeto) │ │ (Já desenvolvida) │ │ │ │ │ │ - Escreve dados │ │ - Lê dados │ │ - Autenticado │ │ - Sem autenticação │ │ - React SPA │ │ - Apenas leitura │ └──────────────────┘ └──────────────────────┘ ▲ WRITE ▲ READ │ │ └────────── Firebase ────────┘ (fonte única de verdade) ``` --- ## Fluxo de Dados ### Atualização de Resultado em Tempo Real ``` Admin clica "+1 Golo" no Live Score Editor │ ▼ React Hook Form / estado local atualiza │ ▼ Firebase transaction (atómica): - Atualiza game.score - Cria evento no subcollection game.events - Atualiza stats do jogador (se golo com autor) - Recalcula standings │ ▼ Firebase Firestore (source of truth) │ ├──▶ Admin Dashboard: listener onSnapshot atualiza UI │ └──▶ App Cliente: listener onSnapshot recebe atualização (< 2 segundos latência) ``` ### Fluxo de Autenticação ``` Utilizador acede ao site │ ▼ Firebase Auth verifica sessão │ ├── Não autenticado ──▶ Redireciona para /login │ └── Autenticado ──▶ Verifica role em Firestore │ ├── role: "admin" ──▶ Acesso total │ └── outros ──▶ Redireciona para /login ``` --- ## Arquitetura de Componentes ``` App ├── AuthProvider (contexto de autenticação) │ └── Router │ ├── /login → LoginPage │ └── ProtectedRoute (requer auth) │ └── Layout │ ├── Sidebar │ ├── Header │ └── Outlet (conteúdo da página) │ ├── /dashboard → DashboardPage │ ├── /games → GamesPage │ │ └── /games/:id/live → LiveGamePage ⭐ │ ├── /clubs → ClubsPage │ │ └── /clubs/:id → ClubDetailPage │ ├── /players → PlayersPage │ ├── /standings → StandingsPage │ └── /scorers → ScorersPage ``` --- ## Gestão de Estado ### Zustand Stores ```typescript // authStore — autenticação { user: FirebaseUser | null, isAdmin: boolean, loading: boolean } // leagueStore — dados da liga (cache local) { clubs: Club[], players: Player[], currentSeason: string, activeGameId: string | null // jogo a decorrer } ``` ### Firebase Hooks (React Query pattern) Cada módulo tem um hook custom que: 1. Subscreve ao Firestore com `onSnapshot` 2. Guarda dados no estado local do hook 3. Expõe funções de mutação (create, update, delete) 4. Faz cleanup do listener no unmount ```typescript // Exemplo function useGames(jornadaId?: string) { const [games, setGames] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { const q = jornadaId ? query(gamesRef, where("jornadaId", "==", jornadaId)) : gamesRef const unsubscribe = onSnapshot(q, (snapshot) => { setGames(snapshot.docs.map(d => ({ id: d.id, ...d.data() }))) setLoading(false) }) return () => unsubscribe() // cleanup }, [jornadaId]) return { games, loading } } ``` --- ## Firebase Security Rules ```javascript rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Função helper: verifica se é admin function isAdmin() { return request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "admin"; } // Jogos: leitura pública, escrita apenas admin match /games/{gameId} { allow read: if true; allow write: if isAdmin(); match /events/{eventId} { allow read: if true; allow write: if isAdmin(); } } // Clubes: leitura pública, escrita apenas admin match /clubs/{clubId} { allow read: if true; allow write: if isAdmin(); } // Jogadores: leitura pública, escrita apenas admin match /players/{playerId} { allow read: if true; allow write: if isAdmin(); } // Standings: leitura pública, escrita apenas admin match /standings/{doc} { allow read: if true; allow write: if isAdmin(); } // Utilizadores: apenas o próprio ou admin match /users/{userId} { allow read: if request.auth.uid == userId || isAdmin(); allow write: if isAdmin(); } } } ```