Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
eb996e9
Add cross-repo suite badges to README
joefarrelly Jun 19, 2026
7fe0241
Merge pull request #34 from joefarrelly/add-readme-badges
joefarrelly Jun 19, 2026
68b4faa
Replace cross-repo badges with suite links
joefarrelly Jun 19, 2026
c760f34
Merge pull request #36 from joefarrelly/add-readme-badges
joefarrelly Jun 19, 2026
b626acd
Add Celery reliability: rate limiting, error handling, stale purge
joefarrelly Jun 21, 2026
cbf5016
Merge pull request #37 from joefarrelly/celery-reliability
joefarrelly Jun 21, 2026
9384c62
Rename DataEquipmentVariant.armour to armor
joefarrelly Jun 21, 2026
c243dd7
Merge pull request #38 from joefarrelly/rename-armour-to-armor
joefarrelly Jun 21, 2026
55a4c54
Add celerybeat-schedule to .gitignore
joefarrelly Jun 28, 2026
dc331ba
Merge pull request #39 from joefarrelly/rename-armour-to-armor
joefarrelly Jun 28, 2026
24581ed
Add achievements and reputations (Phase 2a)
joefarrelly Jun 28, 2026
38c97cc
Remove celerybeat-schedule from tracking
joefarrelly Jun 28, 2026
765f425
Add achievements, reputations, and per-faction collection scan
joefarrelly Jun 28, 2026
53b4598
Merge pull request #40 from joefarrelly/phase-2a-achievements-reputat…
joefarrelly Jun 28, 2026
d041c7d
Add faction expansion mapping, fix rep standing, improve achievement …
joefarrelly Jun 28, 2026
c45d719
Merge pull request #41 from joefarrelly/phase-2a-ui-data-fixes
joefarrelly Jun 28, 2026
f1c7062
Remove keybind feature, keep generic Lua upload pipeline (#43)
joefarrelly Jun 30, 2026
17bfa7a
Fix admin session cookie breaking login via CSRF enforcement (#42)
joefarrelly Jun 30, 2026
3c3ff77
Add Phase 2b: Mythic+ rating tracking (#44)
joefarrelly Jun 30, 2026
5fc5355
Schedule fullDataScan weekly via Celery Beat (#45)
joefarrelly Jun 30, 2026
47a4f79
Serve gold and played time from cached addon file (#46)
joefarrelly Jun 30, 2026
6700fe3
Fix 500 on .lua file upload from closed-file write (#47)
joefarrelly Jun 30, 2026
593e2cc
Fix 500 reading uploaded addon file in text mode (#48)
joefarrelly Jun 30, 2026
4b48b5a
Parse addon gold/played-time into a DB table on upload (#49)
joefarrelly Jun 30, 2026
8072ed0
Remove Codecov integration
joefarrelly Jul 1, 2026
799ad3f
Merge pull request #50 from joefarrelly/remove-codecov
joefarrelly Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,3 @@ jobs:
BLIZZ_SECRET: ci-secret
BLIZZ_REDIRECT_URI: http://localhost:3000/redirect/
run: python -m pytest tests/ -v

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,17 @@ test.py
# claude local settings
.claude/settings.local.json

# Celery
celerybeat-schedule
celerybeat.pid

# Python
__pycache__/
*.pyc
*.pyo
coverage.xml
.coverage
.coverage

# Celery
celerybeat-schedule
celerybeat.pid
63 changes: 45 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FazzToolsAPI

Django REST Framework backend for **FazzTools** — a World of Warcraft companion app. Integrates with the Blizzard Battle.net API to sync character data (professions, equipment, mounts, pets) and parses uploaded WoW Lua addon files to serve keybind data.
Django REST Framework backend for **FazzTools** — a World of Warcraft companion app. Integrates with the Blizzard Battle.net API to sync character data (professions, equipment, mounts, pets) and stores uploaded WoW Lua addon exports for future addon-only data (gold, currencies, lockouts).

The companion frontend lives at `../FazzToolsFrontend` (React, port 3000 in dev).

Expand Down Expand Up @@ -71,12 +71,12 @@ apicore/ The single Django app
serializers.py DRF serializers
permissions.py IsSessionUser permission class
libs/
keybind_builder.py Pure keybind-building logic (build_all/single_keybinds, tier_sort_key)
keybind_mapping.py Slot→action-button mappings per addon
lua_parser.py Hand-rolled Lua-table-to-JSON converter
icon_mapping.py Mount/pet icon mappings
lua_parser.py Hand-rolled Lua-table-to-JSON converter
icon_mapping.py Mount/pet icon mappings
faction_expansion.py Hardcoded faction_id → expansion name mapping (283 factions)
expansion_order.py tier_sort_key — sorts profession tiers by expansion order
migrations/ DB migrations
tests/ pytest suite (47 tests); run via pytest tests/
tests/ pytest suite (33 tests); run via pytest tests/
conftest.py pytest env-var setup (pytest_configure hook)
```

Expand All @@ -96,41 +96,68 @@ conftest.py pytest env-var setup (pytest_configure hook)
- `altprofessiondatas` — `ProfileAltProfessionData`: Known recipes per alt/profession
- `altequipments` — `ProfileAltEquipment`: Equipped gear slots per alt
- `usermounts` / `userpets` — Collected mounts/pets per user
- `altachievements` — `ProfileAltAchievement`: Achievement completions per alt
- `altreputations` — `ProfileAltReputation`: Faction standing per alt
- `altmythicplus` — `ProfileAltMythicPlus`: Current-season M+ rating summary per alt
- `altmythicplusdungeons` — `ProfileAltMythicPlusDungeon`: Best run per dungeon per alt
- `altaddondata` — `ProfileAltAddonData`: Gold + played time per alt, parsed from the uploaded `.lua` file (addon-only, no Blizzard API equivalent)

### Data endpoints (static WoW data, synced via DataScan task)
- `professions`, `professiontiers`, `professionrecipes`, `reagents`, `recipereagents`
- `equipments`, `equipmentvariants`
- `mounts`, `pets`
- `achievements` — `DataAchievement`: All WoW achievements (name, points, category)
- `factions` — `DataFaction`: All WoW reputation factions
- `mythicdungeons` — `DataMythicDungeon`: All Mythic+ dungeons (current and historical)

### Custom endpoints
- `POST /api/custom/bnetlogin/` — Battle.net OAuth2 callback; creates/updates user and syncs alts
- `POST /api/custom/logout/` — Flushes Django session and removes auth state
- `POST /api/custom/scanalt/` — Triggers `fullAltScan` Celery task for a user
- `POST /api/custom/datascan/` — Triggers `fullDataScan` Celery task (Django admin user required)
- `POST /api/custom/datascan/` — Triggers all data scans (Django admin required)
- `POST /api/custom/datascan/professions/` — Triggers profession data scan only
- `POST /api/custom/datascan/mounts/` — Triggers mount data scan only
- `POST /api/custom/datascan/pets/` — Triggers pet data scan only
- `POST /api/custom/datascan/achievements/` — Triggers achievement data scan only
- `POST /api/custom/datascan/factions/` — Triggers faction data scan only
- `POST /api/custom/datascan/mythicdungeons/` — Triggers Mythic+ dungeon catalog scan only

## Key data flows

### Battle.net login
`BnetLogin.create` → exchanges auth code for token (using `Authorization: Bearer` header) → fetches WoW profile → HMAC-hashes Blizzard user ID → upserts `ProfileUser` and all `ProfileAlt` records.

### Alt scan (`fullAltScan` Celery task)
For each alt belonging to a user, fetches from Blizzard API:
1. `/professions` → upserts `ProfileAltProfession` + `ProfileAltProfessionData` (creates missing `DataProfessionRecipe` entries on the fly)
2. `/equipment` → upserts `ProfileAltEquipment` + `DataEquipment` / `DataEquipmentVariant`
3. `/collections/mounts` → links known `DataMount` records to user via `ProfileUserMount`
4. `/collections/pets` → links known `DataPet` records to user via `ProfileUserPet`
Dispatches two sets of tasks in parallel:

**Per-alt** (`scan_single_alt` × N alts):
1. Character summary → updates `ProfileAlt.alt_ilvl` (equipped item level)
2. `/professions` → upserts `ProfileAltProfession` + `ProfileAltProfessionData`
3. `/equipment` → upserts `ProfileAltEquipment` + `DataEquipment` / `DataEquipmentVariant`
4. `/reputations` → upserts `ProfileAltReputation` per faction
5. `/mythic-keystone-profile` → upserts `ProfileAltMythicPlus` + `ProfileAltMythicPlusDungeon`. The main endpoint only lists season refs (no rating/runs) — the season id isn't flagged as "current" anywhere, so `max(season.id)` is used to pick it, then a second call to `/mythic-keystone-profile/season/{id}` fetches `mythic_rating` and `best_runs`. Blizzard can list two `best_runs` entries per dungeon (best-timed and best-overall, same `map_rating` but different `keystone_level`) — the sync keeps the higher level.

**Per-user** (`scan_user_collection` × 1):
Picks the highest-level, highest-ilvl alt per faction (Alliance + Horde) and fetches:
- `/collections/mounts` → links known `DataMount` to user via `ProfileUserMount`
- `/collections/pets` → links known `DataPet` to user via `ProfileUserPet`
- `/achievements` → upserts `ProfileAltAchievement` for that representative alt

### Data scan (`fullDataScan` Celery task)
Fetches Blizzard static data API indexes and walks all professions (tiers → categories → recipes → reagents) and all mounts/pets, creating `Data*` records.
Dispatches six independent subtasks: `scanProfessionData`, `scanMountData`, `scanPetData`, `scanAchievementData`, `scanFactionData`, `scanMythicDungeonData`. Each can also be triggered individually via its own endpoint. The dungeon index requires `namespace=dynamic-eu` (not `static-eu` like other catalogs).

### Scheduled tasks (`CELERY_BEAT_SCHEDULE` in `settings.py`)
- `purge_stale_profiles` — daily, deletes expired profile records.
- `fullDataScan` — weekly, Sunday 03:00 UTC, with `BLIZZ_CLIENT`/`BLIZZ_SECRET` baked into the schedule args at startup. The admin-triggered `/api/custom/datascan/` endpoints still work for ad-hoc/manual scans (e.g. testing a single category).

### Lua keybind file
`ProfileUser.perform_update` validates and stores a `FazzToolsScraper.lua` addon export.
`ProfileUserView.list` with `?page=all` or `?page=single` parses the stored Lua file using the `recursive()` function (a hand-rolled Lua-table-to-JSON converter) and joins results against `ProfileAlt` + Blizzard spell data. Returns per-spec keybind mappings.
### Lua addon file
`ProfileUser.perform_update` validates and stores a `FazzToolsScraper.lua` addon export, then parses it via `LuaParser` and upserts `ProfileAltAddonData` for each alt found in the file (matched by `f"{alt.alt_name}-{alt.alt_realm}"`, the display name + display realm key `core.lua` writes). This is parse-on-upload, not parse-on-read — there's no Celery sync task for this data since it only ever exists in the addon export, never the Blizzard API, so the upload itself is the sync point. A failed parse logs a warning and leaves the stored file/timestamp update intact rather than failing the upload. Currencies/lockouts/keystone/vault are captured by the addon but not yet parsed into a model — same pattern, not built.

## Database tables (all prefixed `ft_`)

**Data (static):** `ft_data_profession`, `ft_data_professiontier`, `ft_data_professionrecipe`, `ft_data_reagent`, `ft_data_recipereagent`, `ft_data_equipment`, `ft_data_equipmentvariant`, `ft_data_mount`, `ft_data_pet`
**Data (static):** `ft_data_profession`, `ft_data_professiontier`, `ft_data_professionrecipe`, `ft_data_reagent`, `ft_data_recipereagent`, `ft_data_equipment`, `ft_data_equipmentvariant`, `ft_data_mount`, `ft_data_pet`, `ft_data_achievement`, `ft_data_faction`, `ft_data_mythicdungeon`

**Profile (user):** `ft_profile_user`, `ft_profile_alt`, `ft_profile_altprofession`, `ft_profile_altprofessiondata`, `ft_profile_altequipment`, `ft_profile_usermount`, `ft_profile_userpet`
**Profile (user):** `ft_profile_user`, `ft_profile_alt`, `ft_profile_altprofession`, `ft_profile_altprofessiondata`, `ft_profile_altequipment`, `ft_profile_usermount`, `ft_profile_userpet`, `ft_profile_altachievement`, `ft_profile_altreputation`, `ft_profile_altmythicplus`, `ft_profile_altmythicplusdungeon`, `ft_profile_altaddondata`

## Things to know

Expand Down
23 changes: 23 additions & 0 deletions apicore/libs/expansion_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
_EXPANSION_ORDER: dict[str, int] = {
"classic": 0,
"outland": 1,
"northrend": 2,
"cataclysm": 3,
"pandaria": 4,
"draenor": 5,
"legion": 6,
"kul tiran": 7,
"zandalari": 7,
"shadowlands": 8,
"dragon isles": 9,
"khaz algar": 10,
"midnight": 11,
}


def tier_sort_key(tier_name: str) -> int:
name_lower = tier_name.lower()
for keyword, order in _EXPANSION_ORDER.items():
if keyword in name_lower:
return order
return 999
Loading
Loading