feat: implement MVP2 companion sharing feature - #8
Conversation
📝 WalkthroughWalkthroughAdds authenticated companion search, add, list, and removal APIs. Member and companion responses now expose latest sleep-jetlag city, coordinates, direction, label, and timestamp data, with airport codes included in city responses. Integration tests cover the new flows. ChangesCompanion and jetlag sharing
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant CompanionController
participant CompanionService
participant CompanionRepository
participant SleepJetlagResultRepository
Client->>CompanionController: Request companion operation
CompanionController->>CompanionService: Delegate authenticated member action
CompanionService->>CompanionRepository: Read or update relationship
CompanionService->>SleepJetlagResultRepository: Read latest jetlag result
CompanionService-->>CompanionController: Build companion response
CompanionController-->>Client: Return ApiResponse
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (8)
src/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberCityResponse.java (1)
6-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate DTO:
MemberCityResponsemirrorsCitySummaryResponsefield-for-field.Both records carry the same six fields (
countryName,cityNameKr,cityNameEn,airportCode,latitude,longitude) and an identicalfrom(City)mapper. This PR had to addairportCodeto both in lockstep — a sign that future field changes are likely to be applied inconsistently.Consider extracting a shared city-location response type reused across
member,sleep, andcompanionDTOs.♻️ Example consolidation
// e.g. src/main/java/com/cotato/cokerthon/global/dto/response/CityLocationResponse.java public record CityLocationResponse( String countryName, String cityNameKr, String cityNameEn, String airportCode, double latitude, double longitude ) { public static CityLocationResponse from(City city) { return new CityLocationResponse( city.getCountryName(), city.getCityNameKr(), city.getCityNameEn(), city.getAirportCode(), city.getLatitude(), city.getLongitude() ); } }Then reference this shared type from
MemberResponse,SleepJetlagResultResponse, and companion DTOs instead of redefining per-domain twins.🤖 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/cotato/cokerthon/domain/member/dto/response/MemberCityResponse.java` around lines 6 - 37, Remove the duplicated city-location DTO structure represented by MemberCityResponse and CitySummaryResponse. Introduce a shared CityLocationResponse with the six common fields and a single from(City) mapper, then update MemberResponse, SleepJetlagResultResponse, and companion DTOs to use it while preserving the existing schema metadata and API contracts.src/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberResponse.java (1)
58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the jetlag label formatter
formatJetlagLabel(int)is duplicated inSleepJetlagResultResponse; move it to a shared helper so the hour/minute formatting stays consistent across both DTOs.🤖 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/cotato/cokerthon/domain/member/dto/response/MemberResponse.java` around lines 58 - 69, Extract the duplicated formatJetlagLabel(int) logic from MemberResponse and SleepJetlagResultResponse into a shared helper, then update both DTOs to call that helper and remove their private implementations, preserving the existing hour/minute output.src/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java (5)
47-52: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert every no-record field is null.
The contract requires all city and jetlag fields to be null when no result exists, but this checks only
city. Also assertjetlagMinutes,jetlagLabel,direction, andlastRecordedAt.🤖 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/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java` around lines 47 - 52, The add-response assertions in CompanionIntegrationTest must verify every no-record field is null, not just city. Extend the assertions for addedData to check jetlagMinutes, jetlagLabel, direction, and lastRecordedAt using the same null-value pattern.
113-129: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winVerify one-way deletion semantics.
This only proves that the requester's list is emptied. Create the reverse relationship first, delete the requester's connection, and assert the other member's connection remains intact.
🤖 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/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java` around lines 113 - 129, Update 동행자를_삭제하면_목록에서_사라진다 to establish the reverse companion relationship before deletion, then delete the requester’s connection and verify the requester’s companion list is empty while the other member’s list still contains its connection. Reuse the existing signup, add, and getCompanions helpers, and assert the remaining relationship belongs to the original requester.
156-179: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winFail fast on signup and login errors.
The helpers ignore signup status and assume
data.accessTokenexists. Assert successful responses and a non-empty token so setup failures identify their actual cause.🤖 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/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java` around lines 156 - 179, Update signupAndLogin and login to fail fast on unsuccessful HTTP responses: assert the signup response is successful, assert the login response is successful, and verify that the parsed data.accessToken is present and non-empty before returning it. Include response details in assertion messages to make setup failures diagnosable.
75-78: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winFail fast if jetlag setup fails.
The jetlag POST response is ignored, so a setup failure appears later as an unrelated companion-card failure. Assert its expected HTTP status before checking the card fields.
🤖 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/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java` around lines 75 - 78, In CompanionIntegrationTest, capture the response from the jetlag setup POST and assert that it returns the expected HTTP status before validating companion-card fields, so setup failures fail at the correct step.
27-40: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCover the
alreadyCompanion=truebranch.This test only verifies the pre-add case. Add the companion, search again, and assert that
alreadyCompanionistrue.🤖 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/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java` around lines 27 - 40, Extend 아이디로_동행자를_검색한다 to add the target user as a companion after the initial assertions, then search for the same user again using the existing companion-add and search helpers. Assert the second response is successful and its data.alreadyCompanion value is true, covering the post-add branch.src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.java (1)
63-65: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftAvoid one sleep-result query per companion.
toCompanionResponseperformsfindFirstByMemberOrderByCreatedAtDescfor every listed companion, producing anN+1query pattern. Batch-load the latest results or use a projection so database load and latency do not grow linearly with the list size.🤖 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/cotato/cokerthon/domain/companion/service/CompanionService.java` around lines 63 - 65, The companion listing in CompanionService should not invoke toCompanionResponse for each companion when that method performs a separate latest-result lookup. Refactor the repository/service flow to batch-load each companion member’s latest result or return the required fields through a projection, then map the preloaded data into responses without per-companion findFirstByMemberOrderByCreatedAtDesc queries.
🤖 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/cotato/cokerthon/domain/companion/service/CompanionService.java`:
- Around line 52-56: Handle the race-prone duplicate insert in the companion
creation flow: update the method containing existsByMemberAndCompanionMember and
Companion.create/save to catch the repository’s unique-constraint violation
(typically DataIntegrityViolationException) and throw
BusinessException(ErrorCode.ALREADY_COMPANION) instead, while preserving other
persistence errors.
---
Nitpick comments:
In
`@src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.java`:
- Around line 63-65: The companion listing in CompanionService should not invoke
toCompanionResponse for each companion when that method performs a separate
latest-result lookup. Refactor the repository/service flow to batch-load each
companion member’s latest result or return the required fields through a
projection, then map the preloaded data into responses without per-companion
findFirstByMemberOrderByCreatedAtDesc queries.
In
`@src/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberCityResponse.java`:
- Around line 6-37: Remove the duplicated city-location DTO structure
represented by MemberCityResponse and CitySummaryResponse. Introduce a shared
CityLocationResponse with the six common fields and a single from(City) mapper,
then update MemberResponse, SleepJetlagResultResponse, and companion DTOs to use
it while preserving the existing schema metadata and API contracts.
In
`@src/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberResponse.java`:
- Around line 58-69: Extract the duplicated formatJetlagLabel(int) logic from
MemberResponse and SleepJetlagResultResponse into a shared helper, then update
both DTOs to call that helper and remove their private implementations,
preserving the existing hour/minute output.
In
`@src/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java`:
- Around line 47-52: The add-response assertions in CompanionIntegrationTest
must verify every no-record field is null, not just city. Extend the assertions
for addedData to check jetlagMinutes, jetlagLabel, direction, and lastRecordedAt
using the same null-value pattern.
- Around line 113-129: Update 동행자를_삭제하면_목록에서_사라진다 to establish the reverse
companion relationship before deletion, then delete the requester’s connection
and verify the requester’s companion list is empty while the other member’s list
still contains its connection. Reuse the existing signup, add, and getCompanions
helpers, and assert the remaining relationship belongs to the original
requester.
- Around line 156-179: Update signupAndLogin and login to fail fast on
unsuccessful HTTP responses: assert the signup response is successful, assert
the login response is successful, and verify that the parsed data.accessToken is
present and non-empty before returning it. Include response details in assertion
messages to make setup failures diagnosable.
- Around line 75-78: In CompanionIntegrationTest, capture the response from the
jetlag setup POST and assert that it returns the expected HTTP status before
validating companion-card fields, so setup failures fail at the correct step.
- Around line 27-40: Extend 아이디로_동행자를_검색한다 to add the target user as a companion
after the initial assertions, then search for the same user again using the
existing companion-add and search helpers. Assert the second response is
successful and its data.alreadyCompanion value is true, covering the post-add
branch.
🪄 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 Plus
Run ID: 5f9f0c0d-1a9f-4f2c-b08b-d94fef9f48df
📒 Files selected for processing (18)
src/main/java/com/cotato/cokerthon/domain/auth/service/AuthService.javasrc/main/java/com/cotato/cokerthon/domain/companion/controller/CompanionController.javasrc/main/java/com/cotato/cokerthon/domain/companion/dto/request/CompanionCreateRequest.javasrc/main/java/com/cotato/cokerthon/domain/companion/dto/response/CompanionCityResponse.javasrc/main/java/com/cotato/cokerthon/domain/companion/dto/response/CompanionResponse.javasrc/main/java/com/cotato/cokerthon/domain/companion/dto/response/CompanionSearchResponse.javasrc/main/java/com/cotato/cokerthon/domain/companion/repository/CompanionRepository.javasrc/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.javasrc/main/java/com/cotato/cokerthon/domain/member/controller/MemberController.javasrc/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberCityResponse.javasrc/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberResponse.javasrc/main/java/com/cotato/cokerthon/domain/member/service/MemberService.javasrc/main/java/com/cotato/cokerthon/domain/sleep/dto/response/CitySummaryResponse.javasrc/main/java/com/cotato/cokerthon/domain/sleep/repository/SleepJetlagResultRepository.javasrc/main/java/com/cotato/cokerthon/global/exception/ErrorCode.javasrc/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.javasrc/test/java/com/cotato/cokerthon/domain/member/MemberIntegrationTest.javasrc/test/java/com/cotato/cokerthon/domain/sleep/SleepJetlagIntegrationTest.java
| if (companionRepository.existsByMemberAndCompanionMember(me, target)) { | ||
| throw new BusinessException(ErrorCode.ALREADY_COMPANION); | ||
| } | ||
|
|
||
| companionRepository.save(Companion.create(me, target)); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant service, entity, repository, and migration files.
git ls-files | rg 'src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService\.java|src/main/java/com/cotato/cokerthon/domain/companion|src/main/resources|src/test' || true
# Show a compact outline of likely relevant Java files.
for f in \
src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.java \
src/main/java/com/cotato/cokerthon/domain/companion/domain/Companion.java \
src/main/java/com/cotato/cokerthon/domain/companion/repository/CompanionRepository.java
do
if [ -f "$f" ]; then
echo "### OUTLINE $f"
ast-grep outline "$f" --view expanded || true
fi
done
# Locate all references to the duplicate check and companion table constraints.
rg -n "existsByMemberAndCompanionMember|ALREADY_COMPANION|Companion.create|unique|constraint|companion_member|member_id" \
src/main/java src/main/resources || trueRepository: 8th-COKERTHON/server-team5
Length of output: 6605
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the relevant source sections with line numbers.
for f in \
src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.java \
src/main/java/com/cotato/cokerthon/domain/companion/domain/Companion.java \
src/main/java/com/cotato/cokerthon/domain/companion/repository/CompanionRepository.java
do
if [ -f "$f" ]; then
echo "### FILE $f"
wc -l "$f"
sed -n '1,220p' "$f" | cat -n
echo
fi
done
# Inspect schema/migrations if present.
for f in $(git ls-files | rg 'src/main/resources/(db/migration|schema|data)/.*\.(sql|yaml|yml|json)$' || true); do
echo "### FILE $f"
wc -l "$f"
sed -n '1,240p' "$f" | cat -n
echo
doneRepository: 8th-COKERTHON/server-team5
Length of output: 5564
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Relevant files only.
for f in \
src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.java \
src/main/java/com/cotato/cokerthon/domain/companion/domain/Companion.java \
src/main/java/com/cotato/cokerthon/domain/companion/repository/CompanionRepository.java
do
if [ -f "$f" ]; then
echo "### $f"
wc -l "$f"
sed -n '1,220p' "$f" | cat -n
echo
fi
done
echo "### SEARCH"
rg -n "existsByMemberAndCompanionMember|ALREADY_COMPANION|Companion.create|`@Table`|uniqueConstraints|`@UniqueConstraint`|companion_member|member_id" \
src/main/java src/main/resources || trueRepository: 8th-COKERTHON/server-team5
Length of output: 9079
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the companion service, entity, and repository with line numbers.
for f in \
src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.java \
src/main/java/com/cotato/cokerthon/domain/companion/domain/Companion.java \
src/main/java/com/cotato/cokerthon/domain/companion/repository/CompanionRepository.java
do
if [ -f "$f" ]; then
echo "### $f"
wc -l "$f"
sed -n '1,240p' "$f" | cat -n
echo
fi
doneRepository: 8th-COKERTHON/server-team5
Length of output: 5554
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for schema/migration-level uniqueness constraints and duplicate handling.
rg -n "`@UniqueConstraint`|uniqueConstraints|`@Table`|ALREADY_COMPANION|existsByMemberAndCompanionMember|companion_member|member_id" \
src/main/java src/main/resources || trueRepository: 8th-COKERTHON/server-team5
Length of output: 3385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the companion package structure.
find src/main/java/com/cotato/cokerthon/domain/companion -maxdepth 4 -type f | sortRepository: 8th-COKERTHON/server-team5
Length of output: 862
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the companion entity and any global exception translation around data integrity violations.
for f in \
src/main/java/com/cotato/cokerthon/domain/companion/entity/Companion.java \
src/main/java/com/cotato/cokerthon/global/exception/GlobalExceptionHandler.java \
src/main/java/com/cotato/cokerthon/global/exception/BusinessException.java
do
if [ -f "$f" ]; then
echo "### FILE $f"
wc -l "$f"
sed -n '1,240p' "$f" | cat -n
echo
fi
done
echo "### SEARCH"
rg -n "DataIntegrityViolationException|ConstraintViolationException|ALREADY_COMPANION|BusinessException|ExceptionHandler|`@RestControllerAdvice`|`@ControllerAdvice`" \
src/main/java || trueRepository: 8th-COKERTHON/server-team5
Length of output: 9966
CompanionService.java:52-56 — Map the duplicate-insert failure to ALREADY_COMPANION. The (member_id, companion_member_id) unique constraint is already on Companion, but the existsBy... check is still race-prone and a concurrent insert can bubble up as a generic 500. Catch the unique-violation here and translate it to ALREADY_COMPANION.
🤖 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/cotato/cokerthon/domain/companion/service/CompanionService.java`
around lines 52 - 56, Handle the race-prone duplicate insert in the companion
creation flow: update the method containing existsByMemberAndCompanionMember and
Companion.create/save to catch the repository’s unique-constraint violation
(typically DataIntegrityViolationException) and throw
BusinessException(ErrorCode.ALREADY_COMPANION) instead, while preserving other
persistence errors.
Summary
GET /api/members/me에 현재 위치(도시/위경도) 포함하도록 확장 — 지구본에 내 위치 표시 가능Test plan
./gradlew build전체 통과Closes #7
Summary by CodeRabbit
New Features
Bug Fixes
Tests