Skip to content

Support API-driven avatar changes: login.avatar_id sync + refresh consumer#1095

Merged
Brutus5000 merged 7 commits into
developfrom
feat/login-avatar-id-sync
Jun 21, 2026
Merged

Support API-driven avatar changes: login.avatar_id sync + refresh consumer#1095
Brutus5000 merged 7 commits into
developfrom
feat/login-avatar-id-sync

Conversation

@Brutus5000

@Brutus5000 Brutus5000 commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

Two related pieces of work to support shifting avatar handling to the API while keeping the lobby fully functional during the transition:

1. Read & write login.avatar_id alongside legacy avatars.selected

The DB now has login.avatar_id as the authoritative single-avatar FK (added in faf/db#331, released as v143). Until all writers move over, both columns must stay consistent.

  • Add avatar_id to the login Table definition (server/db/models.py).
  • PlayerService.fetch_player_data joins through the avatars grant table on login.avatar_id (with ownership check), with a fallback to the legacy selected = 1 row. A stale avatar_id pointing at a no-longer-granted avatar resolves to no row.
  • command_avatar (select action) now also writes login.avatar_id whenever it touches avatars.selected, so the lobby never produces drift between the two columns going forward.
  • Bumps FAF_DB_VERSION to v143 in CI and compose.yaml.

2. New AvatarChangeQueueService consumer

When the API changes a player's avatar (e.g. via the website), the lobby has no idea — its in-memory Player.avatar stays stale until the player reconnects. New consumer reacts to a fresh routing key, re-reads the DB, and marks the player dirty so the existing BroadcastService emits the usual player_info to all connected clients.

Wire contract for publishers (API side, separate PR):

  • exchange: MQ_EXCHANGE_NAME (faf-lobby)
  • routing key: success.player_avatar.update — past-tense event; fan-out friendly so future services can subscribe.
  • payload: {"player_id": <int>, "avatar_id": <int|null>}avatar_id is included so downstream subscribers don't need a DB roundtrip; the lobby itself re-reads from DB to enforce ownership and pick up url/tooltip.

Implementation:

  • PlayerService.refresh_player_avatar(player_id) public method + extracted _fetch_player_avatar helper sharing the ownership-checked join with fetch_player_data.
  • AvatarChangeQueueService follows the ClientMessageQueueService pattern; auto-registered via Service.__init_subclass__.

Why

End state we're working toward: API is the single writer for selected avatar; lobby reacts to API-driven changes and broadcasts via existing machinery. This PR is the lobby half — it keeps reading/writing the legacy column today, and it'll listen for API-driven changes once they start arriving.

Test plan

  • Player with login.avatar_id set → fetch returns that avatar's url/tooltip.
  • Player with login.avatar_id NULL but a legacy avatars.selected = 1 row → fetch returns the legacy avatar (backwards compatibility).
  • Player with login.avatar_id pointing at an avatar no longer granted in avatars → resolves to no avatar (ownership enforced).
  • command_avatar { action: "select", avatar: <url> } → both avatars.selected = 1 AND login.avatar_id updated to that avatar.
  • command_avatar { action: "select", avatar: null }avatars.selected cleared AND login.avatar_id set to NULL.
  • Publish success.player_avatar.update for a connected player → avatar refreshed, player_info broadcast emitted on the next BroadcastService tick.
  • Publish for a disconnected player → logged, no crash.
  • Malformed payloads (non-JSON, missing player_id, non-int player_id) → logged and acked.

Out of scope / follow-ups

  • API-side publish of success.player_avatar.update — separate PR on faf-java-api.
  • Phasing out the SQL-write branch from command_avatar — wait until the API is the sole writer and legacy clients have migrated.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a queue-driven avatar update mechanism that refreshes connected players’ displayed avatars from trusted update events.
  • Bug Fixes
    • Avatar selection is now stored on the login record (including clearing it when no avatar is selected), improving consistency across sessions.
    • Player avatar retrieval and live refresh now prioritize the stored avatar while maintaining legacy fallback behavior.
  • Chores
    • Updated the database migration baseline version used by CI and the compose migration service.
  • Tests
    • Expanded unit test coverage for avatar selection, queue-driven updates, and avatar refresh (connected vs. not connected).

The DB now has login.avatar_id as the authoritative single-avatar FK
(added in faf/db PR #331). Until all writers move over, both columns
must stay consistent. This change:

- Adds avatar_id to the login Table definition.
- Player fetch picks the avatar via COALESCE(login.avatar_id,
  avatars.idAvatar) so the new column wins, with fallback to the
  legacy selected=1 row.
- command_avatar's select action now also writes login.avatar_id
  whenever it updates avatars.selected, so the lobby never produces
  drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new avatar_id foreign-key column is added to the login table referencing avatars_list.id. The player data fetch query is updated to join avatars_list using conditional or_ logic that prefers login.avatar_id when set and falls back to the legacy selected == 1 flag when null. On avatar selection, command_avatar now also writes the chosen (or cleared) avatar_id back to t_login. A new AvatarChangeQueueService consumes external microservice events and calls player_service.refresh_player_avatar() to re-fetch and mark players dirty for broadcasting. Tests verify selection, clearing, refresh, and event handling. Database migration tooling is updated to version v143.

Changes

Authoritative avatar_id on login with local selection

Layer / File(s) Summary
Schema column and player-fetch read query
server/db/models.py, server/player_service.py
login table gains avatar_id FK to avatars_list.id. PlayerService imports or_, constructs _avatar_grant_join_onclause to build the join predicate using or_ logic: prefer avatars.idAvatar == login.avatar_id when login.avatar_id is set; otherwise fall back to avatars.selected == 1 when null. fetch_player_data uses this new onclause when joining avatars.
Avatar selection write to login.avatar_id
server/lobbyconnection.py
command_avatar derives new_avatar_id from the selected avatar row (or None on deselect) and persists it to t_login.avatar_id for the current player.
Tests for avatar selection and clearing
tests/unit_tests/test_lobbyconnection.py
test_command_avatar_select verifies that selecting an avatar stores the matching avatar_id in the login table. test_command_avatar_select_clear verifies that deselecting (avatar=None) clears login.avatar_id to None and removes all selected=1 flags from avatars.

Event-driven avatar refresh via queue service

Layer / File(s) Summary
Player service refresh mechanism
server/player_service.py, tests/unit_tests/test_player_service.py
Adds _fetch_player_avatar() to re-read avatar data using the same ownership-checked join as fetch_player_data, and refresh_player_avatar(player_id) as a public entry point that updates a connected player's avatar and marks them dirty. Returns True when the player was refreshed, False when not connected. Tests verify both connected and disconnected scenarios.
Queue service implementation and export
server/avatar_change_queue_service.py, server/__init__.py
AvatarChangeQueueService listens for microservice events (routing key success.player_avatar.update) containing player_id and optional avatar_id. Declares a per-host queue, validates JSON payloads, and calls refresh_player_avatar() for valid messages. Exports the service via server.__all__ for dependency injection.
Queue service tests
tests/unit_tests/test_avatar_change_queue_service.py
Comprehensive tests cover lifecycle (initialize declares queue/consumer, shutdown cancels consumer), valid/invalid JSON handling, missing/non-integer/boolean player_id validation, avatar cleared scenarios, player-not-connected paths, and integration with the refresh mechanism.

Database migration version update

Layer / File(s) Summary
DB migration version v138 → v143
.github/workflows/test.yml, compose.yaml
CI workflow and Docker Compose service now reference FAF database migrations version v143 to deploy the schema and migration changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A column was born in the login with care,
avatar_id gleaming, the new choice so fair.
When it's set, prefer it with logical flair,
when it's null, pick the flag that was there.
Then events arrive—hop, refresh in mid-air! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding login.avatar_id synchronization and implementing a message queue consumer for avatar updates from the API.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/login-avatar-id-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/lobbyconnection.py (1)

976-984: ⚡ Quick win

Extend avatar selection tests to assert login.avatar_id synchronization.

At Line 978, the new authoritative column is written, but existing coverage (per tests/unit_tests/test_lobbyconnection.py snippet) only checks avatars.selected. Add assertions for login.avatar_id on both select and clear paths to lock the migration contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/lobbyconnection.py` around lines 976 - 984, The avatar selection logic
in the lobbyconnection module now synchronizes the authoritative login.avatar_id
column when avatars are selected. Update the tests in
tests/unit_tests/test_lobbyconnection.py to add assertions that verify
login.avatar_id is correctly synchronized on both the select avatar and clear
avatar (if applicable) paths. For each test case that exercises avatar
selection, assert that the login record's avatar_id matches the selected
avatar_id to ensure the migration contract between the legacy avatars.selected
flag and the new authoritative login.avatar_id column is properly maintained.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/player_service.py`:
- Around line 111-116: The outerjoin using coalesce with login.avatar_id does
not validate that the avatar is owned by the player. Add an EXISTS or join guard
condition to the outerjoin onclause in the coalesce block to ensure that
non-null login.avatar_id values are only matched to avatars_list rows when there
is a corresponding row where both avatars.idUser equals login.id and
avatars.idAvatar equals login.avatar_id, enforcing ownership validation
consistent with the legacy path validated in the join above it.

---

Nitpick comments:
In `@server/lobbyconnection.py`:
- Around line 976-984: The avatar selection logic in the lobbyconnection module
now synchronizes the authoritative login.avatar_id column when avatars are
selected. Update the tests in tests/unit_tests/test_lobbyconnection.py to add
assertions that verify login.avatar_id is correctly synchronized on both the
select avatar and clear avatar (if applicable) paths. For each test case that
exercises avatar selection, assert that the login record's avatar_id matches the
selected avatar_id to ensure the migration contract between the legacy
avatars.selected flag and the new authoritative login.avatar_id column is
properly maintained.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d92c8323-d992-4909-81a0-59f76f19d2a2

📥 Commits

Reviewing files that changed from the base of the PR and between 1b98cda and 7603cb3.

📒 Files selected for processing (3)
  • server/db/models.py
  • server/lobbyconnection.py
  • server/player_service.py

Comment thread server/player_service.py Outdated
Brutus5000 and others added 3 commits June 15, 2026 23:51
v143 adds login.avatar_id, which the avatar fetch and command_avatar
write paths in this PR now reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CodeRabbit flagged two issues on the previous commit:

1. The COALESCE-based avatar join trusted login.avatar_id even if the
   underlying grant in `avatars` had been revoked. Restructure the join
   to route through the grant table in both cases: when avatar_id is
   set, match on idUser+idAvatar (= ownership); otherwise fall back to
   selected=1. A stale avatar_id pointing at a no-longer-granted avatar
   now yields no row.

2. test_command_avatar_select only checked avatars.selected. Add an
   assertion that login.avatar_id mirrors the selected idAvatar, and a
   new test_command_avatar_select_clear covering the null path
   (avatars.selected cleared AND login.avatar_id set to NULL).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the API changes a player's avatar (e.g. via the website), the lobby
has no way to know — its in-memory Player.avatar stays stale until the
player reconnects. Add a RabbitMQ consumer on the new
`success.player_avatar.update` routing key that re-reads the avatar from
the DB and marks the player dirty so BroadcastService emits the usual
player_info to all connected clients on the next tick.

Wire contract (for the API team to implement the publish side):
- exchange: MQ_EXCHANGE_NAME (faf-lobby)
- routing key: success.player_avatar.update
- payload: {"player_id": <int>, "avatar_id": <int|null>}
  avatar_id is informational for downstream subscribers; the lobby
  re-reads from DB to enforce the ownership check and pick up
  url/tooltip.

Includes:
- PlayerService.refresh_player_avatar(player_id) public API + an
  extracted _fetch_player_avatar helper (uses the same ownership-checked
  join as fetch_player_data).
- AvatarChangeQueueService following the ClientMessageQueueService
  pattern; auto-registered via Service.__init_subclass__.
- Unit tests covering valid/invalid payloads, missing player_id, and
  unconnected players, plus a refresh_player_avatar test against the
  shared test DB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Brutus5000 Brutus5000 changed the title Read & write login.avatar_id alongside legacy avatars.selected Support API-driven avatar changes: login.avatar_id sync + refresh consumer Jun 20, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
server/player_service.py (1)

111-117: ⚡ Quick win

Extract the avatar join predicate into a shared helper to prevent drift.

The ownership/fallback predicate is duplicated in two query paths. Centralizing it keeps avatar resolution behavior consistent as migration logic evolves.

♻️ Proposed refactor
+    `@staticmethod`
+    def _avatar_grant_join_onclause():
+        return and_(
+            avatars.c.idUser == login.c.id,
+            or_(
+                avatars.c.idAvatar == login.c.avatar_id,
+                and_(
+                    login.c.avatar_id.is_(None),
+                    avatars.c.selected == 1
+                )
+            )
+        )
...
                 .outerjoin(
                     avatars,
-                    onclause=and_(
-                        avatars.c.idUser == login.c.id,
-                        or_(
-                            avatars.c.idAvatar == login.c.avatar_id,
-                            and_(
-                                login.c.avatar_id.is_(None),
-                                avatars.c.selected == 1
-                            )
-                        )
-                    )
+                    onclause=self._avatar_grant_join_onclause()
                 )
...
             .outerjoin(
                 avatars,
-                onclause=and_(
-                    avatars.c.idUser == login.c.id,
-                    or_(
-                        avatars.c.idAvatar == login.c.avatar_id,
-                        and_(
-                            login.c.avatar_id.is_(None),
-                            avatars.c.selected == 1
-                        )
-                    )
-                )
+                onclause=self._avatar_grant_join_onclause()
             )

Also applies to: 157-163

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/player_service.py` around lines 111 - 117, The avatar join predicate
logic (the or_ condition that checks if idAvatar matches OR if avatar_id is None
and selected equals 1) is duplicated across two query paths in
player_service.py. Extract this predicate into a shared helper function and call
it from both locations (around lines 111-117 and 157-163) to ensure consistent
avatar resolution behavior as the migration logic evolves and prevent future
drift when this logic needs to be updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/avatar_change_queue_service.py`:
- Around line 24-36: The import section has incorrect blank-line spacing that
does not conform to isort standards. Run isort on the
server/avatar_change_queue_service.py file to automatically correct the spacing
between import groups (standard library imports, third-party imports like
aio_pika, and local imports from the current package). This will ensure the
imports are properly formatted with the correct number of blank lines between
each group.
- Around line 94-101: The validation logic for player_id in the payload parsing
section does not reject boolean values, since int(True) and int(False) return 1
and 0 respectively without raising an exception. Before the int conversion on
raw_player_id, add an explicit check to reject boolean types and treat them as
invalid values. Include this boolean type check in the same exception handling
pattern as the existing (TypeError, ValueError) catch block to ensure malformed
messages with boolean player_id values are dropped with an appropriate warning
log.

In `@tests/unit_tests/test_avatar_change_queue_service.py`:
- Around line 6-9: The multiline import statement from
server.avatar_change_queue_service is missing a trailing comma after the last
import item AvatarChangeQueueService, which violates isort formatting standards.
Add a trailing comma after AvatarChangeQueueService in the import block
containing PLAYER_AVATAR_UPDATE_ROUTING_KEY and AvatarChangeQueueService, and
then run isort to automatically format the imports according to the project
standards.

---

Nitpick comments:
In `@server/player_service.py`:
- Around line 111-117: The avatar join predicate logic (the or_ condition that
checks if idAvatar matches OR if avatar_id is None and selected equals 1) is
duplicated across two query paths in player_service.py. Extract this predicate
into a shared helper function and call it from both locations (around lines
111-117 and 157-163) to ensure consistent avatar resolution behavior as the
migration logic evolves and prevent future drift when this logic needs to be
updated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 38739cdd-05d8-480c-abd1-67b35cce84e8

📥 Commits

Reviewing files that changed from the base of the PR and between ae53aba and 99eda4a.

📒 Files selected for processing (5)
  • server/__init__.py
  • server/avatar_change_queue_service.py
  • server/player_service.py
  • tests/unit_tests/test_avatar_change_queue_service.py
  • tests/unit_tests/test_player_service.py

Comment thread server/avatar_change_queue_service.py Outdated
Comment thread server/avatar_change_queue_service.py Outdated
Comment thread tests/unit_tests/test_avatar_change_queue_service.py
Brutus5000 and others added 2 commits June 20, 2026 19:55
CI:
- isort: drop trailing comma in test import and the extra blank line
  before the routing-key constant.
- mypy: annotate raw_player_id as Any so int(payload.get(...)) typechecks
  (matches the pattern in ClientMessageQueueService._dispatch_to_user).

CodeRabbit review:
- Reject bool player_id explicitly; int(True) == 1 would otherwise
  refresh player 1 on every truthy payload. New test covers this.
- Extract the avatar grant-table join onclause into
  _avatar_grant_join_onclause so fetch_player_data and
  _fetch_player_avatar can't drift apart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- D212: module docstring is now a single-line summary, with the wire
  contract moved into the class docstring where it's more discoverable.
- D203: blank line before the class docstring.
- D107: add __init__ docstring (matches ClientMessageQueueService).
- D205/D415/D213 on refresh_player_avatar: restructure as second-line
  summary ending in a period, with a blank line before the description.

D212 and D213 are mutually exclusive pydocstyle rules — the codebase
elsewhere follows D213, so multi-line docstrings keep the
summary-on-second-line style and the module docstring is collapsed to
a one-liner to dodge the conflict entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
server/avatar_change_queue_service.py (1)

96-107: ⚠️ Potential issue | 🟠 Major

Enforce strict integer type for player_id (no coercion).

The wire contract (lines 25-30) explicitly requires player_id to be an int, but line 106 coerces values with int(raw_player_id). This allows malformed payloads like {"player_id": 1.9} or {"player_id": "1"} to pass; int(1.9) silently truncates to 1, potentially refreshing the wrong player. Replace the coercion with a strict isinstance(raw_player_id, int) check (after bool rejection) to enforce the contract.

Suggested patch
             raw_player_id: Any = payload.get("player_id")
-            # Reject bool explicitly: int(True) == 1 would otherwise sneak
-            # through and refresh player 1 on every truthy payload.
-            if isinstance(raw_player_id, bool):
+            # Enforce exact wire contract type: JSON integer only.
+            if not isinstance(raw_player_id, int) or isinstance(raw_player_id, bool):
                 self._logger.warning(
                     "Dropping avatar-update message: invalid player_id %r",
                     raw_player_id,
                 )
                 return
-            try:
-                player_id = int(raw_player_id)
-            except (TypeError, ValueError):
-                self._logger.warning(
-                    "Dropping avatar-update message: invalid player_id %r",
-                    raw_player_id,
-                )
-                return
+            player_id = raw_player_id
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/avatar_change_queue_service.py` around lines 96 - 107, The code
currently coerces raw_player_id to an integer using int(raw_player_id), which
allows malformed payloads like floats (1.9) or strings ("1") to silently pass
through. Replace the try-except block starting at line 106 that calls
int(raw_player_id) with a strict isinstance check for int type (after the
existing bool rejection check). Instead of attempting conversion, check if
raw_player_id is strictly an instance of int, and if not, log a warning and
return early, matching the pattern already established for the bool rejection
check. This enforces the wire contract requirement that player_id must be an
actual integer value, not a coercible value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@server/avatar_change_queue_service.py`:
- Around line 96-107: The code currently coerces raw_player_id to an integer
using int(raw_player_id), which allows malformed payloads like floats (1.9) or
strings ("1") to silently pass through. Replace the try-except block starting at
line 106 that calls int(raw_player_id) with a strict isinstance check for int
type (after the existing bool rejection check). Instead of attempting
conversion, check if raw_player_id is strictly an instance of int, and if not,
log a warning and return early, matching the pattern already established for the
bool rejection check. This enforces the wire contract requirement that player_id
must be an actual integer value, not a coercible value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b86c6be3-ef97-4e84-9c14-dd2d08e1f116

📥 Commits

Reviewing files that changed from the base of the PR and between 99eda4a and 6ba21e8.

📒 Files selected for processing (3)
  • server/avatar_change_queue_service.py
  • server/player_service.py
  • tests/unit_tests/test_avatar_change_queue_service.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/player_service.py

@Brutus5000 Brutus5000 force-pushed the feat/login-avatar-id-sync branch 2 times, most recently from 9a775de to f23b72c Compare June 21, 2026 20:59
Adds an info-level log line whenever a player's avatar changes, covering
both entry points:
- via client connection (command_avatar select action)
- via the RabbitMQ refresh consumer (refresh_player_avatar)

Both log the player id and the resulting avatar URL (or None if cleared)
so on-call can correlate which path triggered any unexpected change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Brutus5000 Brutus5000 force-pushed the feat/login-avatar-id-sync branch from f23b72c to fe3c002 Compare June 21, 2026 21:10
@Brutus5000 Brutus5000 merged commit 49681ba into develop Jun 21, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant