Conversation
📝 WalkthroughWalkthroughThis 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 ChangesProfile Update Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
.claude/features/user/006_update_my_profile.yamlsrc/main/java/com/project/bangjjack/domain/checklist/domain/entity/RoommatePreference.javasrc/main/java/com/project/bangjjack/domain/user/application/dto/request/UpdateMyProfileRequest.javasrc/main/java/com/project/bangjjack/domain/user/application/exception/NotOnboardedException.javasrc/main/java/com/project/bangjjack/domain/user/application/exception/UserErrorCode.javasrc/main/java/com/project/bangjjack/domain/user/application/usecase/UserUseCase.javasrc/main/java/com/project/bangjjack/domain/user/domain/entity/User.javasrc/main/java/com/project/bangjjack/domain/user/presentation/UserController.javasrc/main/java/com/project/bangjjack/domain/user/presentation/response/UserResponseCode.javasrc/test/java/com/project/bangjjack/domain/user/application/usecase/UserUseCaseTest.java
| 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)> |
There was a problem hiding this comment.
🧩 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")
PYRepository: 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)
PYRepository: 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())
PYRepository: 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")
PYRepository: 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)
PYRepository: 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)
PYRepository: 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")
PYRepository: 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")
PYRepository: 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.
| 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 |
There was a problem hiding this comment.
🧩 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:
- 1: https://jakarta.ee/specifications/bean-validation/3.0/apidocs/jakarta/validation/constraints/size
- 2: https://github.com/hibernate/hibernate-validator/blob/d30d626b/documentation/src/main/asciidoc/ch02.asciidoc
- 3: https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/
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.
| @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.
| 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(); |
There was a problem hiding this comment.
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.
🔗 관련 이슈
✅ PR 유형
어떤 변경 사항이 있었나요?
✏️ 작업 내용
프로필 수정 기능을 구현했습니다.
💡 추가 사항
Summary by CodeRabbit
Release Notes