Skip to content

feat: implement MVP2 companion sharing feature - #8

Merged
hamtorygoals merged 1 commit into
developfrom
feat/companion-mvp2
Jul 10, 2026
Merged

feat: implement MVP2 companion sharing feature#8
hamtorygoals merged 1 commit into
developfrom
feat/companion-mvp2

Conversation

@hamtorygoals

@hamtorygoals hamtorygoals commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • 동행자 검색/추가/목록/삭제 API 구현 (단방향, 승낙 절차 없이 즉시 등록)
  • GET /api/members/me에 현재 위치(도시/위경도) 포함하도록 확장 — 지구본에 내 위치 표시 가능
  • 동행자 카드에도 위경도 포함해 지구본에 친구 위치를 함께 표시할 수 있도록 확장

Test plan

  • ./gradlew build 전체 통과
  • 동행자 통합 테스트 6건 (검색/추가/목록(빈 기록·기록 있음)/자기추가 차단/중복추가 차단/삭제)
  • 내 정보 조회 통합 테스트 2건 (기록 전/후 위경도 확인)

Closes #7

Summary by CodeRabbit

  • New Features

    • Added companion search, add, list, and removal functionality.
    • Companion cards now show profile details and sleep-related city, jetlag, direction, and update information when available.
    • Member profiles now include the latest sleep-related city and jetlag details.
    • City summaries now include airport codes.
    • Added validation for self-additions and duplicate companions.
  • Bug Fixes

    • Improved signup and profile data handling when sleep results are unavailable.
  • Tests

    • Added coverage for companion management, profile updates, jetlag results, and validation errors.

@hamtorygoals hamtorygoals linked an issue Jul 10, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Companion and jetlag sharing

Layer / File(s) Summary
Profile and location response contracts
src/main/java/com/cotato/cokerthon/domain/{auth,member,sleep}/...
Member responses now include the latest sleep-jetlag result, city coordinates, direction, label, and timestamp. City summaries include airport codes.
Companion management flow
src/main/java/com/cotato/cokerthon/domain/companion/..., src/main/java/com/cotato/cokerthon/global/exception/ErrorCode.java
Adds companion request/response DTOs, repository queries, service validation and persistence, and authenticated search, add, list, and removal endpoints.
End-to-end validation
src/test/java/com/cotato/cokerthon/domain/{companion,member,sleep}/...
Integration tests cover companion lifecycle operations, jetlag-enriched responses, empty-record handling, validation errors, and airport-code mappings.

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
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.78% 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 summarizes the main change: implementing the MVP2 companion sharing feature.
Linked Issues check ✅ Passed The PR implements the companion search/add/list/delete APIs and member location fields required by issue #7, with matching integration tests.
Out of Scope Changes check ✅ Passed All code changes support the companion sharing feature or the related member/jetlag response updates; no unrelated scope is evident.
✨ 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/companion-mvp2

Comment @coderabbitai help to get the list of available commands.

@hamtorygoals hamtorygoals self-assigned this Jul 10, 2026
@hamtorygoals
hamtorygoals merged commit f2fd8bc into develop Jul 10, 2026
4 checks passed

@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

🧹 Nitpick comments (8)
src/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberCityResponse.java (1)

6-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate DTO: MemberCityResponse mirrors CitySummaryResponse field-for-field.

Both records carry the same six fields (countryName, cityNameKr, cityNameEn, airportCode, latitude, longitude) and an identical from(City) mapper. This PR had to add airportCode to 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, and companion DTOs.

♻️ 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 win

Extract the jetlag label formatter

formatJetlagLabel(int) is duplicated in SleepJetlagResultResponse; 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 win

Assert 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 assert jetlagMinutes, jetlagLabel, direction, and lastRecordedAt.

🤖 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 win

Verify 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 win

Fail fast on signup and login errors.

The helpers ignore signup status and assume data.accessToken exists. 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 win

Fail 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 win

Cover the alreadyCompanion=true branch.

This test only verifies the pre-add case. Add the companion, search again, and assert that alreadyCompanion is true.

🤖 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 lift

Avoid one sleep-result query per companion.

toCompanionResponse performs findFirstByMemberOrderByCreatedAtDesc for every listed companion, producing an N+1 query 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

📥 Commits

Reviewing files that changed from the base of the PR and between dd0ea36 and 98d934b.

📒 Files selected for processing (18)
  • src/main/java/com/cotato/cokerthon/domain/auth/service/AuthService.java
  • src/main/java/com/cotato/cokerthon/domain/companion/controller/CompanionController.java
  • src/main/java/com/cotato/cokerthon/domain/companion/dto/request/CompanionCreateRequest.java
  • src/main/java/com/cotato/cokerthon/domain/companion/dto/response/CompanionCityResponse.java
  • src/main/java/com/cotato/cokerthon/domain/companion/dto/response/CompanionResponse.java
  • src/main/java/com/cotato/cokerthon/domain/companion/dto/response/CompanionSearchResponse.java
  • src/main/java/com/cotato/cokerthon/domain/companion/repository/CompanionRepository.java
  • src/main/java/com/cotato/cokerthon/domain/companion/service/CompanionService.java
  • src/main/java/com/cotato/cokerthon/domain/member/controller/MemberController.java
  • src/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberCityResponse.java
  • src/main/java/com/cotato/cokerthon/domain/member/dto/response/MemberResponse.java
  • src/main/java/com/cotato/cokerthon/domain/member/service/MemberService.java
  • src/main/java/com/cotato/cokerthon/domain/sleep/dto/response/CitySummaryResponse.java
  • src/main/java/com/cotato/cokerthon/domain/sleep/repository/SleepJetlagResultRepository.java
  • src/main/java/com/cotato/cokerthon/global/exception/ErrorCode.java
  • src/test/java/com/cotato/cokerthon/domain/companion/CompanionIntegrationTest.java
  • src/test/java/com/cotato/cokerthon/domain/member/MemberIntegrationTest.java
  • src/test/java/com/cotato/cokerthon/domain/sleep/SleepJetlagIntegrationTest.java

Comment on lines +52 to +56
if (companionRepository.existsByMemberAndCompanionMember(me, target)) {
throw new BusinessException(ErrorCode.ALREADY_COMPANION);
}

companionRepository.save(Companion.create(me, target));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ 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 || true

Repository: 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
done

Repository: 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 || true

Repository: 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
done

Repository: 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 || true

Repository: 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 | sort

Repository: 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 || true

Repository: 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.

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.

MVP2 - 동행자(친구) 수면 국가 공유 기능

1 participant