Skip to content

Profile Service#28

Open
men229 wants to merge 1 commit into
mainfrom
men228/feature/profile
Open

Profile Service#28
men229 wants to merge 1 commit into
mainfrom
men228/feature/profile

Conversation

@men229
Copy link
Copy Markdown
Collaborator

@men229 men229 commented May 26, 2026

No description provided.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added authenticated endpoints to retrieve and update current user profile information
    • Users can now update their username, first name, and last name
    • Integrated avatar upload and management capability for user profiles

Walkthrough

This PR adds a complete user profile update feature with avatar support. It introduces a new authenticated PATCH /api/v1/users/me endpoint via ProfileController, backed by a new ProfileService that handles profile retrieval and conditional updates. The feature integrates S3 for avatar storage through a new avatars bucket in MinIO, enhanced S3Service methods for avatar key/URL generation, and refactored UserMapper that transforms user entities to responses including presigned avatar URLs. Configuration is updated to declare the avatars bucket, and OpenAPI spec defines the new endpoint contracts.

Changes

User Profile Update Feature

Layer / File(s) Summary
API Contract and Response Schemas
openapi-spec/api.yaml
OpenAPI spec defines PATCH /api/v1/users/me endpoint accepting UserProfileUpdateRequest and returning UserAvatarResponse, with 401 and 409 error responses. Request schema includes username, firstName, lastName, and avatarUpdated flag; response schema provides nullable avatarUrl.
MinIO Configuration for Avatar Bucket
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/MinioProperties.java, backend/smartjam-api/src/main/resources/application.yaml
MinioProperties.Buckets adds new @NotBlank avatars field, and application.yaml provides the avatars bucket name in MinIO configuration.
S3 Service Avatar Support
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/S3Service.java
Public key helpers getSubmissionKey and getAvatarsKey construct S3 object paths. Bucket routing is extended to support avatar keys defaulting to the avatars bucket. New generatePresignedUrlForUserAvatar generates presigned PUT URLs for avatar uploads.
User Entity to Response Mapping with Avatar URL
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java
UserMapper refactored from interface to abstract Spring class with S3Service injection. New toUserResponse(UserEntity) mapping populates roles via mapRoles (converts role sets to name lists) and avatarUrl via buildAvatarUrl (returns presigned download URL or null).
Profile Service Business Logic
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/ProfileService.java
getCurrentUserProfile() retrieves current user, loads entity, and returns mapped UserResponse. updateCurrentUserProfile() applies conditional field updates (username, firstName, lastName) with uniqueness validation, generates avatar presigned URL when avatarUpdated flag is set, and returns UserAvatarResponse (TODO: S3 key not persisted to DB).
Profile Controller HTTP Endpoint Wiring
backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/ProfileController.java
New @Controller implementing ProfileApi with constructor-injected ProfileService. Implements getCurrentUserProfile() and updateCurrentUserProfile() endpoints that log, delegate to service, and return results via ResponseEntity.ok().

Sequence Diagram

sequenceDiagram
  participant Client
  participant ProfileController
  participant ProfileService
  participant IdentityService
  participant UserRepository
  participant UserMapper
  participant S3Service
  
  Client->>ProfileController: GET /api/v1/users/me
  ProfileController->>ProfileService: getCurrentUserProfile()
  ProfileService->>IdentityService: getCurrentUserId()
  ProfileService->>UserRepository: findById(userId)
  ProfileService->>UserMapper: toUserResponse(userEntity)
  UserMapper->>S3Service: buildPresignedDownloadUrl(avatarS3Key)
  UserMapper->>ProfileController: UserResponse
  ProfileController->>Client: 200 OK UserResponse
  
  Client->>ProfileController: PATCH /api/v1/users/me (with avatarUpdated=true)
  ProfileController->>ProfileService: updateCurrentUserProfile(request)
  ProfileService->>UserRepository: existsByUsername(newUsername)
  ProfileService->>UserRepository: save(updatedEntity)
  ProfileService->>S3Service: generatePresignedUrlForUserAvatar(userId)
  ProfileService->>ProfileController: UserAvatarResponse with uploadUrl
  ProfileController->>Client: 200 OK UserAvatarResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Satlykovs/SmartJam#21: Extends the same MinIO/S3 plumbing by adding avatars bucket to MinioProperties and enhancing S3Service for avatar key/presigned URL generation.
  • Satlykovs/SmartJam#22: Touches the same S3Service file with prior S3 key/bucket/presigned-URL logic including submissions handling.

Suggested reviewers

  • shershen0
  • ParovozikThomas
  • Satlykovs

Poem

🐰 A rabbit hops with glee so bright,
User profiles now updated right!
Avatar presigned URLs flow,
From S3 buckets down below! 🪣
Mapping roles and images sweet,
Makes the feature now complete! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a pull request description explaining the purpose, changes, and context of this profile service implementation.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Profile Service' directly summarizes the main change: adding a new profile service with controller, endpoints, and supporting infrastructure for user profile management.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch men228/feature/profile

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/ProfileService.java`:
- Around line 48-53: The current check uses
repository.existsByUsername(request.username()) and will throw
UserAlreadyExistsException even if the user re-submits their own username;
change the logic to ignore the current user's record by either first comparing
request.username() to user.getUsername() and skipping assignment when unchanged,
or replace the existence check with a repository call that excludes the current
user (e.g. existsByUsernameAndIdNot(request.username(), user.getId())) and only
throw if that returns true; then call user.setUsername(request.username()) only
when the username is different and not taken by another user.
- Around line 63-68: The avatar key returned by
s3Service.generatePresignedUrlForUserAvatar is not being persisted, so user
profiles lack a durable pointer; update the avatar-update flow in
ProfileService: when request.avatarUpdated() is true, obtain the key via
s3Service.getAvatarsKey(userId), persist that key to the user's record (e.g.,
via the user repository or UserService update method) and then return
UserAvatarResponse with the URL; if you need upload-confirmed semantics, split
into two endpoints (issue presigned PUT URL, then a confirm endpoint that
persists the key), ensuring the persisted field matches what
s3Service.generatePresignedUrlForUserAvatar expects.

In
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/S3Service.java`:
- Around line 85-92: determineBucket currently defaults any unexpected key to
the avatars bucket; change it to validate the key and explicitly map only known
prefixes ("submissions/", "references/", "avatars/") and throw an
IllegalArgumentException (or similar unchecked exception) when key is null or
has an unknown prefix so callers fail fast; update the determineBucket method to
check for "avatars/" as a valid branch and replace the final return with a throw
new IllegalArgumentException(...) that includes the offending key.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3e6f1d93-979c-4579-b8dc-eba093615fb0

📥 Commits

Reviewing files that changed from the base of the PR and between 8419f40 and 0ec3ba7.

📒 Files selected for processing (7)
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/MinioProperties.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/ProfileController.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/ProfileService.java
  • backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/S3Service.java
  • backend/smartjam-api/src/main/resources/application.yaml
  • openapi-spec/api.yaml

Comment on lines +48 to +53
if (request.username() != null && !request.username().isBlank()) {
if (repository.existsByUsername(request.username())) {
throw new UserAlreadyExistsException("Username already exists");
}
user.setUsername(request.username());
}
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 | 🟠 Major | ⚡ Quick win

Avoid false 409 when username is unchanged.

If Line 48 sends the same username the user already owns, Line 49 still returns true and throws conflict. Compare against current username first (or query excluding current user ID).

Suggested fix
-        if (request.username() != null && !request.username().isBlank()) {
-            if (repository.existsByUsername(request.username())) {
+        if (request.username() != null && !request.username().isBlank()) {
+            String newUsername = request.username().trim();
+            if (!newUsername.equals(user.getUsername()) && repository.existsByUsername(newUsername)) {
                 throw new UserAlreadyExistsException("Username already exists");
             }
-            user.setUsername(request.username());
+            user.setUsername(newUsername);
         }
🤖 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
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/ProfileService.java`
around lines 48 - 53, The current check uses
repository.existsByUsername(request.username()) and will throw
UserAlreadyExistsException even if the user re-submits their own username;
change the logic to ignore the current user's record by either first comparing
request.username() to user.getUsername() and skipping assignment when unchanged,
or replace the existence check with a repository call that excludes the current
user (e.g. existsByUsernameAndIdNot(request.username(), user.getId())) and only
throw if that returns true; then call user.setUsername(request.username()) only
when the username is different and not taken by another user.

Comment on lines +63 to +68
if (request.avatarUpdated() != null && request.avatarUpdated()) {
String key = s3Service.getAvatarsKey(userId);
avatarUrl = s3Service.generatePresignedUrlForUserAvatar(key);
}
// TODO: обновить key в бд, нужна новая ручка|валидатор
return new UserAvatarResponse(avatarUrl);
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 | 🟠 Major | 🏗️ Heavy lift

Persist avatar key as part of the avatar-update flow.

Line 67 confirms the key is not stored. That leaves profile/avatar retrieval inconsistent after upload because no durable pointer is saved on the user.

Suggested direction
         if (request.avatarUpdated() != null && request.avatarUpdated()) {
             String key = s3Service.getAvatarsKey(userId);
+            user.setAvatarUrl(key);
             avatarUrl = s3Service.generatePresignedUrlForUserAvatar(key);
         }
-        // TODO: обновить key в бд, нужна новая ручка|валидатор
         return new UserAvatarResponse(avatarUrl);

If you need “upload-confirmed” semantics, split into two endpoints: (1) issue presigned PUT URL, (2) confirm upload + persist key.

🤖 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
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/ProfileService.java`
around lines 63 - 68, The avatar key returned by
s3Service.generatePresignedUrlForUserAvatar is not being persisted, so user
profiles lack a durable pointer; update the avatar-update flow in
ProfileService: when request.avatarUpdated() is true, obtain the key via
s3Service.getAvatarsKey(userId), persist that key to the user's record (e.g.,
via the user repository or UserService update method) and then return
UserAvatarResponse with the URL; if you need upload-confirmed semantics, split
into two endpoints (issue presigned PUT URL, then a confirm endpoint that
persists the key), ensuring the persisted field matches what
s3Service.generatePresignedUrlForUserAvatar expects.

Comment on lines +85 to 92
private String determineBucket(String key) {
if (key != null && key.startsWith("submissions/")) {
return minioProperties.getBuckets().getSubmissions();
} else if (key != null && key.startsWith("references/")) {
return minioProperties.getBuckets().getReferences();
}
return minioProperties.getBuckets().getAvatars();
}
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 | 🟠 Major | ⚡ Quick win

Fail fast on unknown S3 key prefixes instead of defaulting to avatars.

Line 91 currently routes any unexpected prefix to the avatars bucket, which can produce incorrect presigned URLs and mask upstream key bugs. Treat unknown prefixes as invalid input.

Suggested fix
 private String determineBucket(String key) {
     if (key != null && key.startsWith("submissions/")) {
         return minioProperties.getBuckets().getSubmissions();
     } else if (key != null && key.startsWith("references/")) {
         return minioProperties.getBuckets().getReferences();
+    } else if (key != null && key.startsWith("avatars/")) {
+        return minioProperties.getBuckets().getAvatars();
     }
-    return minioProperties.getBuckets().getAvatars();
+    throw new IllegalArgumentException("Unknown S3 key prefix: " + key);
 }
🤖 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
`@backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/S3Service.java`
around lines 85 - 92, determineBucket currently defaults any unexpected key to
the avatars bucket; change it to validate the key and explicitly map only known
prefixes ("submissions/", "references/", "avatars/") and throw an
IllegalArgumentException (or similar unchecked exception) when key is null or
has an unknown prefix so callers fail fast; update the determineBucket method to
check for "avatars/" as a valid branch and replace the final return with a throw
new IllegalArgumentException(...) that includes the offending key.

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.

2 participants