6.7 KiB
Manual FACR Mode (Manual Club Data)
This document describes the manual FACR mode that replaces the automatic FACR scraping when enabled. In manual mode, all club competitions, matches, and tables are stored in the local database and managed via the admin UI or import APIs.
1. Enabling manual mode
Environment variable:
CLUB_DATA_MODE=manual
Values:
auto– default, uses external FACR integration.manual– uses local manual data for club info, matches, and tables.
The public endpoints and JSON shapes stay the same in both modes so the frontend and widgets work unchanged.
2. Data model (manual tables)
Manual mode uses dedicated tables:
ManualCompetition– competition metadata for the primary club.ManualMatch– individual matches (fixtures + results).ManualTableRow– standings rows for each competition.
These are tied to the primary club configured in Settings (club ID + type), same as the automatic mode.
3. Admin UI
Manual data is managed under:
/admin/manual-data(requires admin role)
Features:
- Create, update, delete
ManualCompetitionrecords. - Download CSV templates for matches and tables.
- Import matches and tables from CSV or Excel (XLSX) files.
- Import matches and tables from raw JSON payloads.
The page shows a live overview of all manual competitions for the primary club.
4. Backend admin API
All admin routes are under /api/v1/admin/manual (behind auth + CSRF + admin role).
4.1 Competitions CRUD
GET /api/v1/admin/manual/competitionsPOST /api/v1/admin/manual/competitionsPUT /api/v1/admin/manual/competitions/:idDELETE /api/v1/admin/manual/competitions/:id
Payload example:
{
"code": "A1A",
"name": "SATUM 5. liga mužů",
"external_id": "<competition-uuid>",
"matches_link": "https://www.fotbal.cz/souteze/turnaje/hlavni/...",
"table_link": "https://www.fotbal.cz/souteze/turnaje/table/...",
"team_count": "14"
}
4.2 CSV/XLSX templates
GET /api/v1/admin/manual/matches/templateGET /api/v1/admin/manual/tables/template
These return simple CSV header rows you can open in Excel / Google Sheets and then export back to CSV or XLSX.
4.3 File imports (CSV or XLSX)
Both endpoints accept multipart/form-data with a file field containing either:
.csvtext file, or.xlsxExcel workbook (first sheet is read).
Matches import
POST /api/v1/admin/manual/matches/import- Form field:
file
Expected headers (columns):
competition_codecompetition_external_idroundis_homeopponent_nameopponent_club_linkexternal_match_idkickoff_date(YYYY-MM-DD)kickoff_time(HH:MM)score_fulltimescore_halftimematch_linkvenuenote
Behavior:
- Competition is resolved by
competition_external_id(if present) orcompetition_code. opponent_club_linkis parsed for a UUID and stored asOpponentExternalID.external_match_idor a UUID frommatch_linkis required and used as the match key.- If a match with the same
(competition_id, external_match_id)exists, it is updated, otherwise created.
Response JSON:
{
"imported": 10,
"updated": 5,
"errors": [
"row 3: competition not found for code='A1A' external_id=''"
]
}
Tables import
POST /api/v1/admin/manual/tables/import- Form field:
file
Expected headers:
competition_codecompetition_external_idrankteam_nameteam_club_linkplayedwinsdrawslossesscorepoints
Behavior:
- Competition resolution same as matches.
team_club_linkis parsed for a UUID and stored asExternalTeamID.- For each competition, existing rows are deleted once on the first row, then new
ManualTableRowrecords are inserted.
Response JSON:
{
"imported": 42,
"errors": [
"row 5: competition not found for code='B3A' external_id=''"
]
}
5. JSON import endpoints
These are useful for syncing from external systems or scripts.
5.1 Matches JSON import
POST /api/v1/admin/manual/matches/import-json
Body shape:
{
"items": [
{
"competition_code": "A1A",
"competition_external_id": "<competition-uuid>",
"round": "2. kolo",
"is_home": "home",
"opponent_name": "FC Opponent",
"opponent_club_link": "https://www.fotbal.cz/kluby/<uuid>",
"external_match_id": "<match-uuid>",
"kickoff_date": "2025-03-15",
"kickoff_time": "17:00",
"kickoff": "", // optional RFC3339 alternative
"score_fulltime": "2:1",
"score_halftime": "1:0",
"match_link": "https://is.fotbal.cz/public/zapasy/<uuid>",
"venue": "Kravaře - tráva",
"note": "Dohrávka"
}
]
}
Semantics:
- Competition resolution, opponent UUID parsing, and match keying behave exactly like the CSV/XLSX import.
kickoff(RFC3339) wins overkickoff_date+kickoff_timeif supplied.- Existing matches are updated; new ones are inserted.
5.2 Tables JSON import
POST /api/v1/admin/manual/tables/import-json
Body shape:
{
"items": [
{
"competition_code": "A1A",
"competition_external_id": "<competition-uuid>",
"rank": "1.",
"team_name": "FC Example",
"team_club_link": "https://www.fotbal.cz/kluby/<uuid>",
"played": "13",
"wins": "9",
"draws": "2",
"losses": "2",
"score": "45:17",
"points": "29"
}
]
}
Semantics:
- Competition and team UUIDs resolved the same as in file imports.
- For each competition, existing rows are cleared once, then replaced by the new
itemslist.
6. How public data is built
When CLUB_DATA_MODE=manual:
buildManualClubPayloadconstructs a FACR-likeClubInfoJSON usingManualCompetition,ManualMatch, andManualTableRow.- The prefetch service writes:
facr_club_info.jsonfacr_tables.jsonmatches.json
- Frontend pages (
/matches,/standings, widgets, scoreboard, etc.) consume these JSON files in the same shape as automatic mode.
7. Logos and team matching
Logo resolution in manual mode uses the existing chain:
- Logo API / overrides and local cache.
- Manual club overrides.
- FACR placeholder URLs based on external IDs.
Because opponent and team rows store external UUIDs (from opponent_club_link and team_club_link), manual data can still benefit from logo lookups and alias matching.
8. Summary
- Toggle
CLUB_DATA_MODEto switch between automatic and manual data without changing the frontend. - Use
/admin/manual-datato manage competitions and import data. - Import from CSV, XLSX, or JSON using the endpoints above.
- Manual mode is designed to mirror automatic FACR data structures so all existing widgets and pages keep working.