Skip to content

Commit 0323323

Browse files
rusthpclaude
andcommitted
feat(championships): chain Liquipedia + HLTV in syncAll, document multi-source
Inject LiquipediaService and HltvService into ChampionshipsService and extend syncAll() to enrich tournament data from both sources after the PandaScore pass, applying the existing merge layer (coverage rule + trust priority) so authoritative fields stay intact while empty fields get filled. Failures in any single source are caught so they cannot break the rest of the pipeline. Add docs/ESPORTS_MULTISOURCE.md describing the architecture, trust priority, schema additions (externalIds, dataSource), services, endpoints, and tests for the multi-source esports integration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent eb34901 commit 0323323

16 files changed

Lines changed: 164 additions & 15 deletions

File tree

apps/admin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cmmv-blog-admin",
3-
"version": "0.0.298",
3+
"version": "0.0.299",
44
"private": true,
55
"description": "Blog admin package for CMMV",
66
"type": "module",

apps/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cmmv-blog-api",
3-
"version": "0.0.298",
3+
"version": "0.0.299",
44
"private": true,
55
"description": "Blog API sample",
66
"dev": {

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cmmv-blog",
3-
"version": "0.0.298",
3+
"version": "0.0.299",
44
"private": true,
55
"description": "Blog package for CMMV",
66
"type": "module",

docs/ESPORTS_MULTISOURCE.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Esports Multi-Source Data Integration
2+
3+
This document describes the multi-source integration that powers the
4+
esports tournaments and matches in `@cmmv/blog`.
5+
6+
## Why
7+
8+
`PandaScore` was previously the only source of tournament data. That
9+
created a single point of failure:
10+
11+
- Major events (e.g. PGL Astana 2026) appeared days or weeks late.
12+
- Regional and lower-tier events were not covered at all.
13+
- Token expiration disabled the entire feature.
14+
15+
The blog now reads from multiple sources and merges them through a
16+
trust-prioritised coverage layer so the database is always populated
17+
with the most accurate fields available.
18+
19+
## Sources
20+
21+
| Source | Coverage | Cron | Type |
22+
|--------------|------------------------------------------|---------------|------------------|
23+
| `pandascore` | CS2, Dota2, LoL, Valorant, R6 | every 2h | REST API |
24+
| `liquipedia` | CS2, Dota2, LoL, Valorant (Tier S/A/B) | every 4h | Wiki HTML API |
25+
| `hltv` | CS2 (canonical, fastest signal) | every 6h | RSS feed scraper |
26+
| `draft5` | CS2 BR scene | (existing) | HTML scraper |
27+
| `vlr` | Valorant | (existing) | HTML scraper |
28+
29+
## Trust Priority (`SOURCE_TRUST`)
30+
31+
`hltv (3) > liquipedia (2) > pandascore (1) > draft5/vlr (0)`
32+
33+
Defined in `packages/plugin/api/championships/tournament-merger.utils.ts`.
34+
35+
Coverage rule:
36+
37+
- A source with higher trust may overwrite existing fields when it
38+
reports a non-empty value.
39+
- A source with lower or equal trust may only **fill empty fields**
40+
(it never overwrites data set by a more trusted source).
41+
42+
## Schema Changes
43+
44+
`packages/plugin/contracts/esports-tournament.contract.ts` adds:
45+
46+
- `externalIds: string` — JSON array `[{source, id}]` listing every
47+
external ID known for the tournament. The legacy `externalId` is
48+
retained for backward compatibility (used as fallback lookup).
49+
- `dataSource: string` — the source that owns the record (defaults
50+
to `pandascore`).
51+
52+
## Merge Layer
53+
54+
`tournament-merger.utils.ts` exports:
55+
56+
- `isSameTournament(a, b)` — dedup by normalised slug, or by
57+
`game + name + dates within ±7 days`.
58+
- `mergeExternalIds(existingJson, newEntry)` — upserts an entry in
59+
the `externalIds` JSON array.
60+
- `mergeTournaments(existing, incoming, source)` — applies the
61+
coverage rule field by field. Teams are merged by max count.
62+
63+
Every source service calls `mergeTournaments` (directly or via
64+
`mergeExternalIds`) when upserting so multiple writers stay
65+
consistent.
66+
67+
## Services
68+
69+
### `LiquipediaService` (`liquipedia.service.ts`)
70+
71+
- `syncTournaments(game)` parses Liquipedia `Portal:Tournaments`
72+
pages for `csgo`, `dota2`, `lol`, and `valorant`. Stored under
73+
`dataSource: 'liquipedia'` with `externalIds[].source = 'liquipedia'`.
74+
- `syncMatches(slug, game)` parses the bracket / match-list HTML for
75+
a specific tournament page.
76+
- HTTP uses Liquipedia's `api.php?action=parse` MediaWiki endpoint
77+
with a 1.2 s request delay per Liquipedia's Terms of Use.
78+
- Cron: tournaments `0 */4 * * *`, matches `30 */2 * * *`.
79+
80+
### `HltvService` (`hltv.service.ts`)
81+
82+
- `syncFromRss()` reads `https://www.hltv.org/rss/news`, detects
83+
tournament references via curated `TOURNAMENT_PATTERNS`, and
84+
extracts match results (`Team A VERB Team B`) for upsert into
85+
`EsportsMatchEntity`.
86+
- Marks tournaments as `ongoing` when fresh news (`<48 h`) mentions
87+
them and the DB record is still `upcoming`.
88+
- Cron: `0 */6 * * *`.
89+
90+
## Controller Endpoints
91+
92+
Defined in `championships.controller.ts`:
93+
94+
| Endpoint | Description |
95+
|---------------------------------------|------------------------------------------|
96+
| `GET /esports/sync-liquipedia` | Sync all 4 supported games (or `?game=`) |
97+
| `GET /esports/sync-liquipedia-matches`| Match sync for a single tournament slug |
98+
| `GET /esports/sync-hltv` | Trigger HLTV RSS sync |
99+
100+
## `ChampionshipsService.syncAll()`
101+
102+
`syncAll()` now runs PandaScore first (canonical source) and then
103+
chains Liquipedia (per game) and HLTV. Failures in any single source
104+
are caught and logged so they do not break the rest of the pipeline.
105+
106+
## Tests
107+
108+
See `tests/esports-multisource.test.ts`:
109+
110+
- `mergeExternalIds` — add / update / append / invalid input.
111+
- `isSameTournament` — slug, slug normalisation, game+name+date,
112+
negative cases (different name, dates too far apart).
113+
- `mergeTournaments` — coverage rule (low trust does not overwrite),
114+
high trust overwrites, external-ID merging, team-list preference.
115+
- LiquipediaService date parsing (pure helpers).
116+
- HltvService match result detection (verb regex + team extraction).
117+
118+
Run with: `node node_modules/vitest/vitest.mjs --run tests/esports-multisource.test.ts`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cmmv/blog",
3-
"version": "0.1.297",
3+
"version": "0.1.298",
44
"description": "Blog plugin for CMMV",
55
"keywords": [
66
"cmmv",

packages/access-control/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cmmv/access-control",
3-
"version": "0.1.297",
3+
"version": "0.1.298",
44
"description": "Access Control package for CMMV",
55
"scripts": {
66
"build": "tsup"

packages/affiliate/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cmmv/affiliate",
3-
"version": "0.1.297",
3+
"version": "0.1.298",
44
"description": "Affiliate package for CMMV",
55
"scripts": {
66
"build": "tsup"

packages/ai-content/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cmmv/ai-content",
3-
"version": "0.1.297",
3+
"version": "0.1.298",
44
"description": "AI Content package for CMMV",
55
"scripts": {
66
"build": "tsup"

packages/eslint-config/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@repo/eslint-config",
3-
"version": "0.0.297",
3+
"version": "0.0.298",
44
"main": "index.js",
55
"license": "MIT",
66
"dependencies": {

packages/newsletter/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cmmv/newsletter",
3-
"version": "0.1.297",
3+
"version": "0.1.298",
44
"description": "Newsletter management package for CMMV",
55
"scripts": {
66
"build": "tsup index.ts admin/index.ts client/index.ts --format cjs,esm --dts"

0 commit comments

Comments
 (0)