Skip to content

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

Open
Brutus5000 wants to merge 7 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 7 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 now have a lazily loaded “current avatar” accessible via the API, and avatar changes are validated against existing assignments.
  • Bug Fixes / Improvements
    • Avatar updates now enforce that the selected avatar is assigned to the player; otherwise an error is returned.
    • Successful avatar updates publish an event for downstream propagation.
  • Deprecations
    • AvatarAssignment.isSelected() is deprecated in favor of tracking selection via the player’s current avatar.
  • Tests
    • Added integration coverage for avatar validation hook behavior.
  • Chores
    • Updated database migration image tags to v143 and added a script to push branch images.

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 53 minutes and 37 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: 5eca44d2-cf9f-4f1d-94fa-8962b0f23c7b

📥 Commits

Reviewing files that changed from the base of the PR and between f8f579b and 036e722.

📒 Files selected for processing (1)
  • src/test/java/com/faforever/api/data/hook/PlayerAvatarValidationHookTest.java
📝 Walkthrough

Walkthrough

Adds PlayerAvatarValidationHook and PlayerAvatarUpdateHook lifecycle hooks to secure avatar selection with validation and RabbitMQ notifications. The Player entity gains a getCurrentAvatar() accessor with lazy ManyToOne JPA mapping, owner-only update permission, and Elide PRECOMMIT/POSTCOMMIT lifecycle bindings to the validation and update hooks respectively. Database migrations upgraded to v143, AvatarAssignment.isSelected() marked for deprecation, and a new branch image build script added for CI/CD.

Changes

Player Avatar Update and Validation

Layer / File(s) Summary
Avatar validation hook and error handling
src/main/java/com/faforever/api/error/ErrorCode.java, src/main/java/com/faforever/api/data/hook/PlayerAvatarValidationHook.java, src/test/java/com/faforever/api/data/hook/PlayerAvatarValidationHookTest.java
Defines PlayerAvatarValidationHook as an Elide LifeCycleHook<Player> that validates non-null currentAvatar is assigned to the player via AvatarAssignmentRepository, raising AVATAR_NOT_ASSIGNED error code (213) if no assignment exists; allows null avatars (clearing). Comprehensive JUnit 5 test coverage verifies assignment validation, rejection of unassigned avatars, and allowance of avatar clearing without repository interaction.
Avatar update hook and RabbitMQ publishing
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 on avatar update.
Player entity mapping and lifecycle hook binding
src/main/java/com/faforever/api/data/domain/Player.java
Adds getCurrentAvatar() with a lazy @ManyToOne mapping via avatar_id, @UpdatePermission(IsEntityOwner.EXPRESSION) restricting updates to the player owner, and two @LifeCycleHookBinding annotations wiring PlayerAvatarValidationHook at UPDATE PRECOMMIT and PlayerAvatarUpdateHook at UPDATE POSTCOMMIT. Includes @Audit entry for avatar selection changes. Corresponding imports added for Jakarta persistence and Elide binding types.
Database schema migration updates
compose.yaml, src/inttest/java/com/faforever/api/config/MainDbTestContainers.java
Updates faf-db-migrations container image from v140 to v143 in both runtime and integration test configurations to enable the new avatar tracking schema.
Legacy avatar assignment deprecation
src/main/java/com/faforever/api/data/domain/AvatarAssignment.java
Marks isSelected() as @Deprecated(forRemoval = true) with Javadoc explaining that selected avatar tracking is now via Player#getCurrentAvatar() and the login.avatar_id column.
Branch image build and push script
push-branch-image.sh
Utility script for Docker image management: validates branch name (rejects develop, main, master, HEAD), sanitizes to Docker-compliant tag format, conditionally builds jar via gradlew bootJar (skippable with SKIP_BUILD=1), and publishes image using docker buildx build --push with configurable platform and provenance disabled.

Sequence Diagram

sequenceDiagram
  participant Client
  participant ElideRuntime
  participant PlayerAvatarValidationHook
  participant AvatarAssignmentRepository
  participant PlayerAvatarUpdateHook
  participant RabbitTemplate

  Client->>ElideRuntime: PATCH /players/{id} (update currentAvatar)
  ElideRuntime->>ElideRuntime: check UpdatePermission (IsEntityOwner)
  ElideRuntime->>PlayerAvatarValidationHook: execute(UPDATE, PRECOMMIT, player)
  PlayerAvatarValidationHook->>AvatarAssignmentRepository: findOneByAvatarIdAndPlayerId(avatarId, playerId)
  AvatarAssignmentRepository->>PlayerAvatarValidationHook: Optional<AvatarAssignment>
  alt assignment exists
    PlayerAvatarValidationHook->>ElideRuntime: validation passes
  else no assignment
    PlayerAvatarValidationHook->>ElideRuntime: throw ApiException (AVATAR_NOT_ASSIGNED)
  end
  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})
  RabbitTemplate->>Client: message published
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A bunny guards their avatar choice so bright,
Validation hooks ensure the selection's right,
If avatars are unassigned, we cry foul—
Then RabbitMQ whispers the news to the crowd,
With player_id and avatar_id in tow,
The lobby hops along with this flow! 🎩✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 change: adding a currentAvatar foreign key to Player and publishing avatar update events via RabbitMQ, which are the core objectives demonstrated across the modified Player.java, new hook classes, and RabbitMQ integration.
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 thread src/main/java/com/faforever/api/data/domain/Player.java
Brutus5000 and others added 5 commits June 21, 2026 23:58
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>
The selected avatar is now tracked via login.avatar_id
(Player.currentAvatar). The legacy `selected` flag remains for
backwards compatibility with older clients and is slated for removal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the audit log that existed on the deprecated
AvatarAssignment.selected flag so avatar selection changes via the new
login.avatar_id path are recorded too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds the current branch's jar and pushes a linux/amd64 Docker image
tagged after the branch name, for deploying test builds. Refuses to run
on develop/main/master. Mirrors the script in the lobby server repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
IsEntityOwner only ensures the caller edits their own Player row, not
that the avatar is granted to them. Add a PRECOMMIT LifeCycleHook that
verifies an AvatarAssignment exists for the player before allowing the
currentAvatar update, rejecting unowned avatars with AVATAR_NOT_ASSIGNED.
Clearing the avatar (null) remains allowed.

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

Actionable comments posted: 2

🤖 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 `@push-branch-image.sh`:
- Around line 29-32: The tag sanitization for the Docker image does not fully
validate Docker tag requirements. The sed substitution replaces invalid
characters but can still produce invalid tags if the first character becomes a
hyphen or dot (Docker requires first character to be alphanumeric or
underscore), or if the resulting tag exceeds 128 characters. After the sed
substitution that creates the tag variable, add logic to remove any leading
hyphens or dots from the tag and truncate the tag to a maximum of 128 characters
to ensure complete Docker compliance before passing it to the docker buildx
command.

In
`@src/test/java/com/faforever/api/data/hook/PlayerAvatarValidationHookTest.java`:
- Line 21: The import statement for assertThrows is using the JUnit 4 version
from org.junit.Assert, but the test class PlayerAvatarValidationHookTest is
using JUnit Jupiter annotations (`@Test`, `@BeforeEach`, `@ExtendWith`). Change the
static import to use org.junit.jupiter.api.Assertions.assertThrows instead of
org.junit.Assert.assertThrows to align with the Jupiter test framework being
used throughout the test class.
🪄 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: a36f9019-8128-42af-b983-8127394cec2b

📥 Commits

Reviewing files that changed from the base of the PR and between 48e6d4b and f8f579b.

📒 Files selected for processing (5)
  • push-branch-image.sh
  • src/main/java/com/faforever/api/data/domain/Player.java
  • src/main/java/com/faforever/api/data/hook/PlayerAvatarValidationHook.java
  • src/main/java/com/faforever/api/error/ErrorCode.java
  • src/test/java/com/faforever/api/data/hook/PlayerAvatarValidationHookTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/faforever/api/data/domain/Player.java

Comment thread push-branch-image.sh
Comment on lines +29 to +32
# Docker tags must match [A-Za-z0-9_][A-Za-z0-9_.-]{0,127}.
# Replace anything else (most commonly '/' from branch prefixes) with '-'.
tag=$(printf '%s' "$branch" | sed 's/[^A-Za-z0-9_.-]/-/g')

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 | 🟡 Minor | ⚡ Quick win

Ensure sanitized tag is always Docker-valid before invoking buildx.

Current sanitization replaces invalid chars, but it can still yield an invalid first character (./-) or exceed 128 chars, causing avoidable late failures in docker buildx.

Suggested patch
-# Docker tags must match [A-Za-z0-9_][A-Za-z0-9_.-]{0,127}.
-# Replace anything else (most commonly '/' from branch prefixes) with '-'.
-tag=$(printf '%s' "$branch" | sed 's/[^A-Za-z0-9_.-]/-/g')
+# Docker tags must match [A-Za-z0-9_][A-Za-z0-9_.-]{0,127}.
+# Replace invalid chars, normalize leading '.'/'-', and cap length.
+tag=$(
+  printf '%s' "$branch" \
+    | sed 's/[^A-Za-z0-9_.-]/-/g' \
+    | sed 's/^[.-][.-]*/_/' \
+    | cut -c1-128
+)
+
+if [ -z "$tag" ]; then
+  echo "Refusing to push: sanitized tag is empty for branch '$branch'." >&2
+  exit 1
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Docker tags must match [A-Za-z0-9_][A-Za-z0-9_.-]{0,127}.
# Replace anything else (most commonly '/' from branch prefixes) with '-'.
tag=$(printf '%s' "$branch" | sed 's/[^A-Za-z0-9_.-]/-/g')
# Docker tags must match [A-Za-z0-9_][A-Za-z0-9_.-]{0,127}.
# Replace invalid chars, normalize leading '.'/'-', and cap length.
tag=$(
printf '%s' "$branch" \
| sed 's/[^A-Za-z0-9_.-]/-/g' \
| sed 's/^[.-][.-]*/_/' \
| cut -c1-128
)
if [ -z "$tag" ]; then
echo "Refusing to push: sanitized tag is empty for branch '$branch'." >&2
exit 1
fi
🤖 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 `@push-branch-image.sh` around lines 29 - 32, The tag sanitization for the
Docker image does not fully validate Docker tag requirements. The sed
substitution replaces invalid characters but can still produce invalid tags if
the first character becomes a hyphen or dot (Docker requires first character to
be alphanumeric or underscore), or if the resulting tag exceeds 128 characters.
After the sed substitution that creates the tag variable, add logic to remove
any leading hyphens or dots from the tag and truncate the tag to a maximum of
128 characters to ensure complete Docker compliance before passing it to the
docker buildx command.

Comment thread src/test/java/com/faforever/api/data/hook/PlayerAvatarValidationHookTest.java Outdated
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