Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Danielsl4/TFG_DAM_2526/llms.txt

Use this file to discover all available pages before exploring further.

FutsalLeague Manager is composed of five distinct layers: an Angular single-page application served by the browser, a Node.js/Express REST API that handles all business logic, a PostgreSQL database for persistent storage, a Redis cache for high-frequency read paths, and Cloudinary for binary asset storage. Each layer communicates over well-defined contracts — HTTP/JSON between the SPA and the API, the pg connection pool between the API and PostgreSQL, ioredis between the API and Redis, and the Cloudinary v2 SDK for image uploads.

Layers at a glance

LayerTechnologyDefault portRole
FrontendAngular 20 SPA, Bootstrap 5, Bootstrap Icons4200User interface — fixtures, standings, admin panel, referee tools
Backend APINode.js, Express 43000REST API, auth, business logic, cron jobs
DatabasePostgreSQL5432Persistent storage for all league data (15 tables)
CacheRedis (ioredis)6379Match data, standings, rate limiting
StorageCloudinary v2, SharpTeam and player image upload and optimization
AuthJWT (jsonwebtoken), bcryptToken issuance, password hashing, role-based middleware

Database schema

The database is initialized by init_db.js, which creates 5 PostgreSQL enums and 15 tables in dependency order. Enums
EnumValues
match_phasefase_de_grupos, octavos, cuartos, semis, final
match_statuspendiente, en_curso, finalizado
event_typegol, tarjeta_amarilla, tarjeta_roja
roleadmin, referee, user
vote_typelocal, empate, visitante
Tables
TableDescription
seasonsTop-level container for each competition year
groupsDivision of teams within a season
teamsClub records with kit color, logo, delegate, and coach
playersPlayer registry with birth date and photo
team_playersMany-to-many: player-team membership per season with jersey number
fieldsVenue records used when scheduling matches
matchesFull match record including phase, status, score, locking metadata
match_eventsIndividual in-game events (goals, yellow/red cards) per match
usersAccounts with role, verification status, and password reset tokens
user_pointsPrediction game score per user per season
team_followersMany-to-many: users following teams
team_statsAggregated win/draw/loss/goal/card statistics per team per group per season
player_statsAggregated goals, cards, and appearances per player per season
match_votesPre-match outcome predictions (one vote per user per match)
audit_logsTimestamped record of every admin action with JSONB details

Request flow

A typical authenticated browser request follows this path:
  1. Angular SPA constructs an HTTP request with an Authorization: Bearer <token> header and sends it to the Express API at http://localhost:3000.
  2. verifyToken middleware decodes and validates the JWT using the JWT_SECRET. If the token is invalid or absent the request is rejected with 401.
  3. Role middleware (verifyAdmin or verifyReferee) checks the role claim on the decoded token. Insufficient privileges return 403.
  4. Route handler queries PostgreSQL via the pg connection pool (max 50 connections, 5 s timeout). For read-heavy endpoints (standings, match lists) the handler checks Redis first and writes through on a cache miss.
  5. Response is serialized as JSON and returned to Angular, which updates the view.
For image uploads, the route handler passes the file buffer through Sharp for resizing and compression before uploading to Cloudinary. The returned CDN URL is stored in the logo_url or photo_url column.

Key design patterns

In-flight request deduplication

The cache layer uses an inflightRequests map to coalesce concurrent identical requests for the same resource into a single upstream database query. When multiple requests arrive simultaneously for an uncached key, only the first triggers a database call; the rest await the same promise. This prevents the thundering herd problem during cache invalidation under load.

Soft deletes

Teams, players, matches, fields, and users carry an is_active boolean column (default TRUE). Deletion through the admin panel sets is_active = FALSE rather than removing the row, preserving referential integrity and historical match records.

Audit logging

Every write operation performed through the admin panel is recorded in audit_logs. Each row captures the acting user (user_id), the action name (action), the affected entity type and ID, a JSONB details blob with before/after values, and a creation timestamp. This provides a full audit trail for compliance and debugging.

Transactional match finalization

When a referee finalizes a match, the operation runs inside a PostgreSQL transaction that atomically:
  1. Updates matches.status to finalizado
  2. Recalculates and upserts rows in team_stats for both home and away teams
  3. Upserts player_stats for every player who scored or received a card
  4. Awards prediction points in user_points for every user whose match_votes row matches the final result
If any step fails, the transaction is rolled back and no partial state is committed.

Match locking

To prevent edit conflicts between referees, the matches table has locked_by (user FK) and locked_at (timestamp) columns. The verifyMatchLock middleware checks these fields before every write. A lock expires automatically after 2 minutes of inactivity — the middleware cleans up expired locks proactively on the next request.

Where to go next

Backend deployment

Configure the Express API for production — process management, environment variables, and health checks.

Frontend deployment

Build and deploy the Angular SPA — static hosting, environment files, and base href configuration.

Database deployment

Provision PostgreSQL, run migrations, configure connection pooling, and set up backups.

Build docs developers (and LLMs) love