Skip to content

Feat/#66 프로필 수정 기능 구현#67

Merged
1winhyun merged 5 commits into
mainfrom
feat/#66
May 28, 2026
Merged

Feat/#66 프로필 수정 기능 구현#67
1winhyun merged 5 commits into
mainfrom
feat/#66

Conversation

@1winhyun

@1winhyun 1winhyun commented May 28, 2026

Copy link
Copy Markdown
Collaborator

🔗 관련 이슈


✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 리팩토링
  • 코드에 영향을 주지 않는 변경사항(주석, 개행 등등..)
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 테스트 코드 추가

✏️ 작업 내용

프로필 수정 기능을 구현했습니다.

  • 자신의 기본 정보와 룸메이트 선호도를 한번에 수정하도록 구현했습니다.
  • 온보딩을 완료한 사용자만 동작할 수 있습니다.


💡 추가 사항

Summary by CodeRabbit

Release Notes

  • New Features
    • Users can now edit their profile including personal information (birth year, grade, gender, campus, department, semester, and dormitory).
    • Users can update roommate preference factors with three priority selections.
    • Profile updates are restricted to users with completed onboarding status.

Review Change Stack

@1winhyun 1winhyun requested a review from xEzIxX May 28, 2026 15:29
@1winhyun 1winhyun self-assigned this May 28, 2026
@1winhyun 1winhyun added ✅Test 테스트 코드 작성 ✨Feat 새로운 기능 개발 labels May 28, 2026
@1winhyun 1winhyun linked an issue May 28, 2026 that may be closed by this pull request
1 task
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces a profile-update feature that allows authenticated users to edit their academic and personal profile fields (birth year, grade, gender, campus, department, semester, dormitory) together with roommate preference factors via a new PATCH endpoint at /api/v1/users/me/profile, with validation, onboarding enforcement, and optional roommate-preference creation or update.

Changes

Profile Update Feature

Layer / File(s) Summary
Feature specification and design
.claude/features/user/006_update_my_profile.yaml
Complete feature spec documenting the PATCH endpoint, request/response contracts, cross-layer flow, validation rules, persistence expectations, error codes (including new 40204 NOT_ONBOARDED), component additions, and test strategy.
Request DTO with validation constraints
src/main/java/com/project/bangjjack/domain/user/application/dto/request/UpdateMyProfileRequest.java
UpdateMyProfileRequest record with non-null constraints, grade bounds (1–4), and fixed-size preference list (exactly 3 factors) enforced via Jakarta Bean Validation.
Exception and error code definitions
src/main/java/com/project/bangjjack/domain/user/application/exception/NotOnboardedException.java, src/main/java/com/project/bangjjack/domain/user/application/exception/UserErrorCode.java
NotOnboardedException and new NOT_ONBOARDED enum constant (code 40204, CONFLICT status) block profile updates from non-onboarded users.
Domain entity profile and preference update methods
src/main/java/com/project/bangjjack/domain/user/domain/entity/User.java, src/main/java/com/project/bangjjack/domain/checklist/domain/entity/RoommatePreference.java
User.editProfile(...) updates profile fields without affecting onboarding flags; RoommatePreference.update(...) refreshes the three priority factors.
Use-case orchestration and business logic
src/main/java/com/project/bangjjack/domain/user/application/usecase/UserUseCase.java
UserUseCase.updateMyProfile(...) validates birth year and duplicate preferences, enforces onboarding status, updates user profile, and either updates existing roommate preferences or creates new ones with registration synchronization via injected PreferenceCreateService.
Controller endpoint and response code
src/main/java/com/project/bangjjack/domain/user/presentation/UserController.java, src/main/java/com/project/bangjjack/domain/user/presentation/response/UserResponseCode.java
New PATCH /me/profile endpoint validates the request, delegates to use case, and returns MY_PROFILE_UPDATED (200 OK) response code.
Comprehensive test coverage
src/test/java/com/project/bangjjack/domain/user/application/usecase/UserUseCaseTest.java
Tests cover happy paths (update existing and create new preferences), validation errors (onboarding, birth year bounds, duplicate factors), and dependency failures (user not found), with Mockito wiring for PreferenceCreateService.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Bangjjack/Bangjjack-Backend#51: Implements the complementary profile GET endpoint (UserController.getMyProfile / UserUseCase.getMyProfile), sharing the same user-profile component layer.
  • Bangjjack/Bangjjack-Backend#40: Introduces roommate-preference factors used in this PR's upsert logic and related profile presentation DTOs.

Suggested reviewers

  • xEzIxX

Poem

🐰 A rabbit hops through profile fields with care,
Birth year, grade, and dorm updates to share,
Roommate preferences, three in a row,
Validation gates where the onboarded go,
PATCH by PATCH, the profile now glows! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ 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 clearly and specifically describes the main change: implementing a profile editing feature (프로필 수정 기능 구현), which aligns with the PR's core objective of allowing users to update their basic information and roommate preferences.
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 feat/#66

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: 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 @.claude/features/user/006_update_my_profile.yaml:
- Around line 48-68: YAML parsing fails because unquoted colons inside the type
scalars like "String (Enum: Gender)" break yaml.safe_load; update the entries
for campus, departmentId (if applicable), semester, dormitory, and preferences
so the entire type value is quoted (e.g., "String (Enum: Gender)" or
'List<String (Enum: RoommatePreferenceFactor)>') to ensure the colon in "Enum:"
is treated as part of the scalar.

In
`@src/main/java/com/project/bangjjack/domain/user/application/dto/request/UpdateMyProfileRequest.java`:
- Line 23: The preferences field in UpdateMyProfileRequest only validates the
list reference and its size but allows null elements; change the declaration to
use a type-use nullity annotation so each item is non-null (i.e., declare
preferences as List<`@NotNull` RoommatePreferenceFactor> and keep the existing
`@NotNull` and `@Size` on the field), and add the corresponding import for the
type-use `@NotNull` used by your validation provider so each element is validated
as non-null before persistence.

In
`@src/main/java/com/project/bangjjack/domain/user/application/usecase/UserUseCase.java`:
- Around line 94-105: The current read-then-create flow using
roommatePreferenceGetService.findByUser followed by
preferenceCreateService.createPreference is race-prone; change this to a
concurrency-safe upsert inside a transaction: wrap the logic in a `@Transactional`
service method that either (a) uses the repository to findByUser with a
PESSIMISTIC_WRITE lock (or a findByUserForUpdate helper) and then updates
existing via RoommatePreference.update or creates and saves a new
RoommatePreference via RoommatePreference.create + repository.save, or (b) if
you prefer optimistic handling, catch the
unique-constraint/DataIntegrityViolationException thrown by
preferenceCreateService.createPreference, then immediately re-query
roommatePreferenceGetService.findByUser and apply update; ensure you still call
user.completeRoommatePreferenceRegistration() after a successful create/update
and keep the changes in the same transactional boundary.
🪄 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: 6f586e21-50af-4f6f-bd6e-ef298bf3f6ad

📥 Commits

Reviewing files that changed from the base of the PR and between df44a57 and c16fd58.

📒 Files selected for processing (10)
  • .claude/features/user/006_update_my_profile.yaml
  • src/main/java/com/project/bangjjack/domain/checklist/domain/entity/RoommatePreference.java
  • src/main/java/com/project/bangjjack/domain/user/application/dto/request/UpdateMyProfileRequest.java
  • src/main/java/com/project/bangjjack/domain/user/application/exception/NotOnboardedException.java
  • src/main/java/com/project/bangjjack/domain/user/application/exception/UserErrorCode.java
  • src/main/java/com/project/bangjjack/domain/user/application/usecase/UserUseCase.java
  • src/main/java/com/project/bangjjack/domain/user/domain/entity/User.java
  • src/main/java/com/project/bangjjack/domain/user/presentation/UserController.java
  • src/main/java/com/project/bangjjack/domain/user/presentation/response/UserResponseCode.java
  • src/test/java/com/project/bangjjack/domain/user/application/usecase/UserUseCaseTest.java

Comment on lines +48 to +68
type: String (Enum: Gender)
required: true
validation: "@NotNull, MALE | FEMALE"
- name: campus
type: String (Enum: Campus)
required: true
validation: "@NotNull, GLOBAL_CAMPUS | MEDICAL_CAMPUS"
- name: departmentId
type: Long
required: true
validation: "@NotNull, departments.id 참조 (없으면 404)"
- name: semester
type: String (Enum: Semester)
required: true
validation: "@NotNull, SIXTEEN_WEEKS | TWENTY_FIVE_WEEKS"
- name: dormitory
type: String (Enum: Dormitory)
required: true
validation: "@NotNull, DORM_1 | DORM_2 | DORM_3"
- name: preferences
type: List<String (Enum: RoommatePreferenceFactor)>

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

yamllint .claude/features/user/006_update_my_profile.yaml
python - <<'PY'
import yaml, pathlib
p = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
yaml.safe_load(p.read_text(encoding="utf-8"))
print("YAML parse OK")
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 2416


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- file around line 48 ----"
nl -ba .claude/features/user/006_update_my_profile.yaml | sed -n '40,60p'

echo "---- python yaml.safe_load parse error (if any) ----"
python - <<'PY'
import yaml, pathlib
p = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
try:
    yaml.safe_load(p.read_text(encoding="utf-8"))
    print("YAML parse OK")
except Exception as e:
    print("YAML parse FAILED:", type(e).__name__, e)
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 162


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- file around lines 40-60 ----"
if command -v cat >/dev/null 2>&1 && command -v awk >/dev/null 2>&1; then
  awk 'NR>=40 && NR<=60 {printf "%5d:%s\n", NR, $0}' .claude/features/user/006_update_my_profile.yaml
else
  sed -n '40,60p' .claude/features/user/006_update_my_profile.yaml
fi

echo "---- python yaml.safe_load parse error (if any) ----"
python3 - <<'PY'
import yaml, pathlib, traceback
p = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
try:
    yaml.safe_load(p.read_text(encoding="utf-8"))
    print("YAML parse OK")
except Exception as e:
    print("YAML parse FAILED:", type(e).__name__, e)
    # Try to surface line/column if provided
    if hasattr(e, 'problem_mark') and e.problem_mark:
        mark = e.problem_mark
        print(f"problem_mark: line={getattr(mark,'line',None)+1}, column={getattr(mark,'column',None)}")
    # keep output short
    # print(traceback.format_exc())
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 1274


🏁 Script executed:

python3 - <<'PY'
import yaml, pathlib
p = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
yaml.safe_load(p.read_text(encoding="utf-8"))
print("YAML parse OK")
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 4396


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".claude/features/user/006_update_my_profile.yaml"
echo "---- matching unquoted Enum types (show line numbers) ----"
python3 - <<'PY'
import re, pathlib
p = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
lines = p.read_text(encoding="utf-8").splitlines()
pat = re.compile(r'^\s*type:\s*[^"\n]*\(Enum:\s*[^)\n]*\)')
for i, line in enumerate(lines, start=1):
    if pat.search(line):
        print(f"{i}:{line}")
PY

echo "---- lines 60-80 for context ----"
sed -n '60,80p' "$FILE"

Repository: Bangjjack/Bangjjack-Backend

Length of output: 1332


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import pathlib, re, yaml

path = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
txt = path.read_text(encoding="utf-8")

# Quote specific unquoted type scalars that contain "(Enum: ...)"
def repl(m):
    full = m.group(0)
    val = m.group(1)
    return re.sub(r'^(\s*type:\s*)(.*)$', r'\1"'+val+'"' , full)

pattern = re.compile(r'^(\s*type:\s*)([^"\n]*\(\s*Enum:\s*[^)\n]*\)\s*)$', re.MULTILINE)
txt2 = pattern.sub(lambda m: m.group(1) + '"' + m.group(2).rstrip() + '"', txt)

try:
    yaml.safe_load(txt2)
    print("YAML parse OK after quoting Enum-bearing type scalars")
except Exception as e:
    print("YAML parse FAILED after quoting:", type(e).__name__, e)
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 307


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import pathlib, re, yaml

path = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
lines = path.read_text(encoding="utf-8").splitlines(True)  # keep newlines

def needs_quote(line: str) -> bool:
    stripped = line.lstrip()
    if not stripped.startswith("type:"):
        return False
    # already quoted?
    after = stripped[len("type:"):].lstrip()
    if after.startswith('"') or after.startswith("'"):
        return False
    return "Enum:" in line

out = []
for line in lines:
    if needs_quote(line):
        indent = line[:len(line)-len(line.lstrip())]
        after = line.lstrip()[len("type:"):].lstrip()
        # Quote entire remainder of the scalar on the line (no YAML comments present in this file section)
        # Preserve trailing newline.
        nl = "\n" if line.endswith("\n") else ""
        after_no_nl = after.rstrip("\r\n")
        out.append(f'{indent}type: "{after_no_nl}"{nl}')
    else:
        out.append(line)

txt2 = "".join(out)
try:
    yaml.safe_load(txt2)
    print("YAML parse OK after quoting full Enum-bearing type values")
except Exception as e:
    print("YAML parse FAILED after quoting:", type(e).__name__, e)
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 129


🏁 Script executed:

python3 - <<'PY'
import yaml, pathlib
p = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
yaml.safe_load(p.read_text(encoding="utf-8"))
print("YAML parse OK")
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 4396


🏁 Script executed:

python3 - <<'PY'
import yaml, pathlib
p = pathlib.Path(".claude/features/user/006_update_my_profile.yaml")
yaml.safe_load(p.read_text(encoding="utf-8"))
print("YAML parse OK")
PY

Repository: Bangjjack/Bangjjack-Backend

Length of output: 4396


Fix YAML parsing error by quoting type values with Enum:

yaml.safe_load fails at line 48:31 (type: String (Enum: Gender)) with mapping values are not allowed here due to the unquoted Enum: ... colon inside the plain scalar. Quote the full type scalar on lines 48/52/60/64/68.

🛠️ Suggested fix
-          - name: gender
-            type: String (Enum: Gender)
+          - name: gender
+            type: "String (Enum: Gender)"
...
-          - name: campus
-            type: String (Enum: Campus)
+          - name: campus
+            type: "String (Enum: Campus)"
...
-          - name: semester
-            type: String (Enum: Semester)
+          - name: semester
+            type: "String (Enum: Semester)"
...
-          - name: dormitory
-            type: String (Enum: Dormitory)
+          - name: dormitory
+            type: "String (Enum: Dormitory)"
...
-          - name: preferences
-            type: List<String (Enum: RoommatePreferenceFactor)>
+          - name: preferences
+            type: "List<String (Enum: RoommatePreferenceFactor)>"
📝 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
type: String (Enum: Gender)
required: true
validation: "@NotNull, MALE | FEMALE"
- name: campus
type: String (Enum: Campus)
required: true
validation: "@NotNull, GLOBAL_CAMPUS | MEDICAL_CAMPUS"
- name: departmentId
type: Long
required: true
validation: "@NotNull, departments.id 참조 (없으면 404)"
- name: semester
type: String (Enum: Semester)
required: true
validation: "@NotNull, SIXTEEN_WEEKS | TWENTY_FIVE_WEEKS"
- name: dormitory
type: String (Enum: Dormitory)
required: true
validation: "@NotNull, DORM_1 | DORM_2 | DORM_3"
- name: preferences
type: List<String (Enum: RoommatePreferenceFactor)>
type: "String (Enum: Gender)"
required: true
validation: "`@NotNull`, MALE | FEMALE"
- name: campus
type: "String (Enum: Campus)"
required: true
validation: "`@NotNull`, GLOBAL_CAMPUS | MEDICAL_CAMPUS"
- name: departmentId
type: Long
required: true
validation: "`@NotNull`, departments.id 참조 (없으면 404)"
- name: semester
type: "String (Enum: Semester)"
required: true
validation: "`@NotNull`, SIXTEEN_WEEKS | TWENTY_FIVE_WEEKS"
- name: dormitory
type: "String (Enum: Dormitory)"
required: true
validation: "`@NotNull`, DORM_1 | DORM_2 | DORM_3"
- name: preferences
type: "List<String (Enum: RoommatePreferenceFactor)>"
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 48-48: syntax error: mapping values are not allowed here

(syntax)

🤖 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 @.claude/features/user/006_update_my_profile.yaml around lines 48 - 68, YAML
parsing fails because unquoted colons inside the type scalars like "String
(Enum: Gender)" break yaml.safe_load; update the entries for campus,
departmentId (if applicable), semester, dormitory, and preferences so the entire
type value is quoted (e.g., "String (Enum: Gender)" or 'List<String (Enum:
RoommatePreferenceFactor)>') to ensure the colon in "Enum:" is treated as part
of the scalar.

@NotNull Long departmentId,
@NotNull Semester semester,
@NotNull Dormitory dormitory,
@NotNull @Size(min = 3, max = 3) List<RoommatePreferenceFactor> preferences

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

🧩 Analysis chain

🌐 Web query:

In Jakarta Bean Validation (Hibernate Validator), does @NotNull @Size(min=3, max=3) Listvalidate list elements as non-null, or isList<@NotNull Enum> required?

💡 Result:

Yes—if you write @NotNull @SiZe(min=3, max=3) List<Enum>, Hibernate Validator treats the @NotNull as applying to the list/container itself (i.e., the list reference must not be null), but it does NOT automatically validate that the list elements are non-null. To require that each list element is non-null, you must put the constraint on the type argument, e.g. List<@NotNull Enum> (or List<@NotNull @SiZe(...) Enum> depending on the element constraint). Why: - @Size supports Collection/List size and “null elements are considered valid,” meaning the size check does not fail just because the collection contains null values [1]. - Hibernate Validator explicitly supports “container element constraints” and “will validate each element” when applying constraints on an Iterable type argument [2]. Hibernate’s reference guide also shows the pattern @NotNull @SiZe(min = 1) public List<@NotNull Customer> getCustomers() where @NotNull on the type argument is what enforces non-null elements [3]. So, for your exact cases: - @NotNull @SiZe(min=3, max=3) List<Enum>: validates (a) the list itself is non-null, and (b) the list size is exactly 3, but allows null elements because @Size treats null elements as valid [1]. - List<@NotNull Enum>: validates that every list element is non-null via container element constraints [2], even though you would still need @Size(min=3, max=3) on the list/container if you also want exactly 3 elements.

Citations:


Enforce non-null elements for preferences
@NotNull @SiZe(min=3, max=3) List<RoommatePreferenceFactor> validates the list reference and its size, but does not reject null elements; add a type-use @NotNull so each item is non-null before persistence.

🛠️ Suggested fix
-        `@NotNull` `@Size`(min = 3, max = 3) List<RoommatePreferenceFactor> preferences
+        `@NotNull` `@Size`(min = 3, max = 3) List<`@NotNull` RoommatePreferenceFactor> preferences
📝 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
@NotNull @Size(min = 3, max = 3) List<RoommatePreferenceFactor> preferences
`@NotNull` `@Size`(min = 3, max = 3) List<`@NotNull` RoommatePreferenceFactor> preferences
🤖 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/project/bangjjack/domain/user/application/dto/request/UpdateMyProfileRequest.java`
at line 23, The preferences field in UpdateMyProfileRequest only validates the
list reference and its size but allows null elements; change the declaration to
use a type-use nullity annotation so each item is non-null (i.e., declare
preferences as List<`@NotNull` RoommatePreferenceFactor> and keep the existing
`@NotNull` and `@Size` on the field), and add the corresponding import for the
type-use `@NotNull` used by your validation provider so each element is validated
as non-null before persistence.

Comment on lines +94 to +105
Optional<RoommatePreference> existing = roommatePreferenceGetService.findByUser(user);
if (existing.isPresent()) {
existing.get().update(preferences.get(0), preferences.get(1), preferences.get(2));
} else {
RoommatePreference preference = RoommatePreference.create(
user,
preferences.get(0),
preferences.get(1),
preferences.get(2)
);
preferenceCreateService.createPreference(preference);
user.completeRoommatePreferenceRegistration();

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

Make preference upsert concurrency-safe.

Current read-then-create flow is race-prone under concurrent PATCH calls and can surface a unique-constraint failure as 500.

🛠️ Suggested hardening approach
+import org.springframework.dao.DataIntegrityViolationException;
...
         Optional<RoommatePreference> existing = roommatePreferenceGetService.findByUser(user);
         if (existing.isPresent()) {
             existing.get().update(preferences.get(0), preferences.get(1), preferences.get(2));
         } else {
             RoommatePreference preference = RoommatePreference.create(
                     user,
                     preferences.get(0),
                     preferences.get(1),
                     preferences.get(2)
             );
-            preferenceCreateService.createPreference(preference);
-            user.completeRoommatePreferenceRegistration();
+            try {
+                preferenceCreateService.createPreference(preference);
+            } catch (DataIntegrityViolationException e) {
+                roommatePreferenceGetService.getByUser(user)
+                        .update(preferences.get(0), preferences.get(1), preferences.get(2));
+            }
+            user.completeRoommatePreferenceRegistration();
         }
🤖 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/project/bangjjack/domain/user/application/usecase/UserUseCase.java`
around lines 94 - 105, The current read-then-create flow using
roommatePreferenceGetService.findByUser followed by
preferenceCreateService.createPreference is race-prone; change this to a
concurrency-safe upsert inside a transaction: wrap the logic in a `@Transactional`
service method that either (a) uses the repository to findByUser with a
PESSIMISTIC_WRITE lock (or a findByUserForUpdate helper) and then updates
existing via RoommatePreference.update or creates and saves a new
RoommatePreference via RoommatePreference.create + repository.save, or (b) if
you prefer optimistic handling, catch the
unique-constraint/DataIntegrityViolationException thrown by
preferenceCreateService.createPreference, then immediately re-query
roommatePreferenceGetService.findByUser and apply update; ensure you still call
user.completeRoommatePreferenceRegistration() after a successful create/update
and keep the changes in the same transactional boundary.

@1winhyun 1winhyun merged commit aebd26e into main May 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feat 새로운 기능 개발 ✅Test 테스트 코드 작성

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 프로필 수정 기능 구현

1 participant