Skip to content

Add Player.currentAvatar FK and publish avatar update events#1146

Open
Brutus5000 wants to merge 2 commits into
developfrom
feat/player-avatar-id-and-rabbit-event
Open

Add Player.currentAvatar FK and publish avatar update events#1146
Brutus5000 wants to merge 2 commits into
developfrom
feat/player-avatar-id-and-rabbit-event

Conversation

@Brutus5000

@Brutus5000 Brutus5000 commented Jun 21, 2026

Copy link
Copy Markdown
Member

What

Exposes the login.avatar_id FK column (added in faf/db v143) on the Player entity as a currentAvatar relationship, making the API a writer for a player's currently selected avatar.

When a player's currentAvatar is updated, a success.player_avatar.update message is published to the faf-lobby topic exchange so the lobby server can refresh its in-memory state and broadcast a player_info to connected clients.

Changes

  • Player.java — new currentAvatar @ManyToOne mapped to the avatar_id column, exposed via Elide with IsEntityOwner update permission and a POSTCOMMIT LifeCycleHook.
  • PlayerAvatarUpdateHook.java (new) — publishes {"player_id": <int>, "avatar_id": <int|null>} to exchange faf-lobby with routing key success.player_avatar.update.

Wire contract

  • Exchange: faf-lobby (topic)
  • Routing key: success.player_avatar.update
  • Payload: {"player_id": <int>, "avatar_id": <int|null>}

The lobby-side consumer is implemented separately (FAForever/server PR #1095).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Players can now access and retrieve their current avatar through the API.
  • Chores

    • Implemented avatar update event messaging to enhance real-time communication of avatar changes across the system.

Expose login.avatar_id on the Player entity as `currentAvatar` so the
API becomes the writer for a player's selected avatar. On update, a
`success.player_avatar.update` message is published on the `faf-lobby`
exchange so the lobby server can refresh its in-memory state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Brutus5000, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 54 minutes and 38 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22952670-aeaf-4a14-bd47-183fc1e67351

📥 Commits

Reviewing files that changed from the base of the PR and between 51e66fc and 96e58ca.

📒 Files selected for processing (2)
  • compose.yaml
  • src/inttest/java/com/faforever/api/config/MainDbTestContainers.java
📝 Walkthrough

Walkthrough

Adds a PlayerAvatarUpdateHook Spring component that publishes a RabbitMQ message containing player_id and avatar_id to the FAF lobby exchange on player avatar update. The Player entity gains a getCurrentAvatar() accessor with a lazy ManyToOne JPA mapping, owner-only update permission, and an Elide POSTCOMMIT lifecycle binding to the new hook.

Changes

Player Avatar Update Hook

Layer / File(s) Summary
PlayerAvatarUpdateHook implementation
src/main/java/com/faforever/api/data/hook/PlayerAvatarUpdateHook.java
New Spring component with ROUTING_KEY_PLAYER_AVATAR_UPDATE constant and RabbitTemplate dependency. The execute method reads the nullable current avatar, assembles a {player_id, avatar_id} payload map, logs it at debug level, and publishes to EXCHANGE_FAF_LOBBY using the routing key.
Player entity wiring
src/main/java/com/faforever/api/data/domain/Player.java
Adds getCurrentAvatar() with a lazy @ManyToOne mapping via avatar_id, @UpdatePermission(IsEntityOwner.EXPRESSION), and a @LifeCycleHookBinding for UPDATE at POSTCOMMIT targeting PlayerAvatarUpdateHook. Corresponding imports added.

Sequence Diagram

sequenceDiagram
  participant Client
  participant ElideRuntime
  participant PlayerAvatarUpdateHook
  participant RabbitTemplate

  Client->>ElideRuntime: PATCH /players/{id} (update currentAvatar)
  ElideRuntime->>ElideRuntime: check UpdatePermission (IsEntityOwner)
  ElideRuntime->>ElideRuntime: persist avatar_id change
  ElideRuntime->>PlayerAvatarUpdateHook: execute(UPDATE, POSTCOMMIT, player)
  PlayerAvatarUpdateHook->>RabbitTemplate: convertAndSend(EXCHANGE_FAF_LOBBY, "success.player_avatar.update", {player_id, avatar_id})
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A bunny changed their avatar today,
A hook was fired without delay,
RabbitMQ received the news,
player_id and avatar_id too,
The lobby knows which hat I wore—
Hoppy updates, nothing more! 🎩

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 captures the main objective of the PR: adding a currentAvatar foreign key relationship and publishing avatar update events to RabbitMQ.
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/player-avatar-id-and-rabbit-event

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

🤖 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 `@src/main/java/com/faforever/api/data/domain/Player.java`:
- Around line 41-51: The getCurrentAvatar() method in the Player class has
permission checks but lacks validation that the avatar being assigned actually
belongs to the player through AvatarAssignment. Add a PRECOMMIT phase hook
(similar to the existing PlayerAvatarUpdateHook which is in POSTCOMMIT) or use a
custom validator that leverages
AvatarAssignmentRepository.findOneByAvatarIdAndPlayerId to verify the avatar is
assigned to the player before allowing the update to proceed. This validation
should throw an appropriate exception if the avatar is not assigned to the
player.
🪄 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: 08ae3f29-710e-4b2f-95d9-8e92b5cb6298

📥 Commits

Reviewing files that changed from the base of the PR and between 39eea66 and 51e66fc.

📒 Files selected for processing (2)
  • src/main/java/com/faforever/api/data/domain/Player.java
  • src/main/java/com/faforever/api/data/hook/PlayerAvatarUpdateHook.java

Comment on lines +41 to +51
@UpdatePermission(expression = IsEntityOwner.EXPRESSION)
@LifeCycleHookBinding(
operation = LifeCycleHookBinding.Operation.UPDATE,
phase = LifeCycleHookBinding.TransactionPhase.POSTCOMMIT,
hook = PlayerAvatarUpdateHook.class
)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "avatar_id")
public Avatar getCurrentAvatar() {
return currentAvatar;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for validation logic ensuring currentAvatar is an assigned avatar

# Look for any security checks or validators related to avatar assignment
rg -n -C3 'avatar.*assignment|assigned.*avatar' --type java -i

# Check if there's a database constraint on avatar_id
fd -e sql -e java | xargs rg -l 'avatar_id' | head -10 | xargs rg -n 'avatar_id' -C2

# Look for any Elide security checks on Avatar relationships
rg -n 'AvatarAssignment' --type java -C3 | head -60

Repository: FAForever/faf-java-api

Length of output: 40604


🏁 Script executed:

# Check the full PlayerAvatarUpdateHook implementation
cat -n src/main/java/com/faforever/api/data/hook/PlayerAvatarUpdateHook.java

Repository: FAForever/faf-java-api

Length of output: 1998


🏁 Script executed:

# Search for any validators or security checks related to avatar updates on Player
rg -n 'class.*Validator|validateAvatar|checkAvatar' --type java

# Look for Elide hooks or checks on Player class
rg -n '@.*Hook|@.*Check' src/main/java/com/faforever/api/data/domain/Player.java -A 1 -B 1

Repository: FAForever/faf-java-api

Length of output: 960


🏁 Script executed:

# Search for integration tests that update currentAvatar
rg -n 'currentAvatar|getCurrentAvatar' --type java -A 3 -B 1 | head -80

Repository: FAForever/faf-java-api

Length of output: 1760


🏁 Script executed:

# Check if there are any tests that try to update currentAvatar with unassigned avatar
rg -n 'currentAvatar|setCurrentAvatar' --type java -B 3 -A 5 | head -120

Repository: FAForever/faf-java-api

Length of output: 1763


🏁 Script executed:

# Look for any security checks or permission validators in the codebase
rg -n 'currentAvatar.*Permission|Permission.*currentAvatar' --type java -B 2 -A 2

Repository: FAForever/faf-java-api

Length of output: 48


🏁 Script executed:

# Check if there's a custom setter or validation on Player.setCurrentAvatar
fd -e java -path '*/domain/*' | xargs rg -l 'class Player' | xargs cat -n | grep -A 150 'setCurrentAvatar\|getCurrentAvatar'

Repository: FAForever/faf-java-api

Length of output: 15376


🏁 Script executed:

# Search for any tests that update currentAvatar
fd -e java -path '*inttest*' -o -path '*test*' | xargs rg -l 'currentAvatar\|setCurrentAvatar' 2>/dev/null

Repository: FAForever/faf-java-api

Length of output: 237


🏁 Script executed:

# Look for PRECOMMIT hooks or any validation hooks
rg -n 'TransactionPhase.PRECOMMIT|`@PrePersist`|`@PreUpdate`|validates.*avatar' --type java

Repository: FAForever/faf-java-api

Length of output: 156


🏁 Script executed:

# Check if AvatarAssignmentRepository methods are used anywhere for validation
rg -n 'findOneByAvatarIdAndPlayerId|findOneByAvatarAndPlayer' --type java -B 2 -A 2

Repository: FAForever/faf-java-api

Length of output: 918


🏁 Script executed:

# Find where AvatarAssignmentRepository is injected and used
rg -n 'AvatarAssignmentRepository' --type java -A 10 | grep -v test

Repository: FAForever/faf-java-api

Length of output: 908


🏁 Script executed:

# Look for any Player update/service logic that might validate avatars
fd -name '*Service.java' -path '*/player/*' -o -name '*PlayerService.java' | xargs cat 2>/dev/null | head -150

Repository: FAForever/faf-java-api

Length of output: 297


🏁 Script executed:

# Check if there's any Elide custom check/hook for Player avatar updates
rg -n 'class.*Check' --type java | grep -i avatar

Repository: FAForever/faf-java-api

Length of output: 202


🏁 Script executed:

# Find where AvatarAssignmentRepository is actually used (not just declared)
rg -n 'avatarAssignmentRepository\.' --type java -B 2 -A 3

Repository: FAForever/faf-java-api

Length of output: 48


🏁 Script executed:

# Check WriteAvatarCheck implementation
cat -n src/main/java/com/faforever/api/security/elide/permission/WriteAvatarCheck.java

Repository: FAForever/faf-java-api

Length of output: 864


🏁 Script executed:

# Search for any endpoint or controller that handles Player updates
fd -name '*Controller.java' | xargs rg -l 'player\|Player' | head -3 | xargs cat | head -200

Repository: FAForever/faf-java-api

Length of output: 297


🏁 Script executed:

# Search for any validators with custom logic
find src/main/java -name '*Validator.java' -type f | xargs grep -l avatar

Repository: FAForever/faf-java-api

Length of output: 50


🏁 Script executed:

# Search for any aspect or interceptor that might validate
rg -n '`@Aspect`|`@Interceptor`' --type java

Repository: FAForever/faf-java-api

Length of output: 135


🏁 Script executed:

# Check if there are any database constraints on avatar_id
rg -n 'FOREIGN KEY|CONSTRAINT.*avatar' src/ 2>/dev/null | head -20

Repository: FAForever/faf-java-api

Length of output: 48


Verify avatar assignment validation exists.

The IsEntityOwner permission ensures only the player can update their own currentAvatar, but it doesn't validate that the specified avatar is one actually assigned to them via AvatarAssignment. A player could potentially set any avatar ID. While AvatarAssignmentRepository provides methods to check avatar-player relationships (findOneByAvatarIdAndPlayerId), these are not used to validate the update. Consider adding validation in the update path (either as a Elide hook in PRECOMMIT phase or a custom validator) to ensure the avatar is assigned to the player.

🤖 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 `@src/main/java/com/faforever/api/data/domain/Player.java` around lines 41 -
51, The getCurrentAvatar() method in the Player class has permission checks but
lacks validation that the avatar being assigned actually belongs to the player
through AvatarAssignment. Add a PRECOMMIT phase hook (similar to the existing
PlayerAvatarUpdateHook which is in POSTCOMMIT) or use a custom validator that
leverages AvatarAssignmentRepository.findOneByAvatarIdAndPlayerId to verify the
avatar is assigned to the player before allowing the update to proceed. This
validation should throw an appropriate exception if the avatar is not assigned
to the player.

The Player.currentAvatar mapping reads login.avatar_id, added in
faf-db v143. Bump the integration-test and compose migration image
so the column exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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