20260422 #27 행성 지역 해금 도메인 구현#42
Hidden character warning
Conversation
Walkthrough탐험(Exploration) 도메인을 재구현하여 행성과 지역의 계층적 노드 관리, 사용자별 해금 진행도 추적, 연료 소모를 통한 해금 시스템을 구현했습니다. 공통 예외 인프라, JPA 엔티티/저장소, 비즈니스 로직 서비스, REST API, Flyway 마이그레이션, 종합 테스트 및 API 문서를 포함합니다. ChangesExploration Domain - Complete Implementation
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes 이 PR은 새로운 도메인의 전체 스택(예외, 엔티티, 저장소, 서비스, DTO, 컨트롤러, 마이그레이션, 테스트, 문서)을 포함하는 주요 기능 구현입니다. 서비스 로직의 연료 차감/해금 흐름, 선행 조건 게이트, 트랜잭션 원자성, 복잡한 테스트 시나리오(Mock 상호작용)로 인해 높은 검토 난이도를 요구합니다. 다만 일관된 패턴과 명확한 의도로 인해 극도로 복잡하지는 않습니다. Possibly Related Issues
Possibly Related PRs
🚥 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: 12
🧹 Nitpick comments (6)
docs/api-specs/05_exploration.md (1)
395-405: ⚡ Quick win코드 블록 언어를 명시해 MD040 경고를 제거해주세요.
현재 처리 로직 fenced block에 언어 태그가 없어 markdownlint(MD040) 경고가 발생합니다.
```text또는```sql처럼 명시해 주세요.🤖 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 `@docs/api-specs/05_exploration.md` around lines 395 - 405, The fenced code block containing the SQL-like transaction (starts with "BEGIN TRANSACTION;") is missing a language tag which triggers markdownlint MD040; update that fenced block to include a language identifier such as ```sql (or ```text) so the block becomes ```sql ... ``` and the linter warning is resolved.docs/superpowers/specs/2026-05-29-exploration-domain-design.md (1)
53-69: ⚡ Quick win마크다운 린트 경고(MD040/MD028/MD058)를 정리해주세요.
코드펜스 언어 태그 누락, blockquote 내부 공백 줄, 테이블 주변 공백 누락이 섞여 있습니다. 문서 CI 안정성을 위해 한 번에 정리하는 걸 권장합니다.
Also applies to: 125-137, 140-145, 148-157, 160-167, 169-171, 279-283, 295-298
🤖 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 `@docs/superpowers/specs/2026-05-29-exploration-domain-design.md` around lines 53 - 69, Fix the Markdown lint issues by adding missing fence language tags to all code blocks (e.g., the directory tree block should be ```text or ```bash), removing extra blank lines inside any blockquotes so there's no leading/trailing empty lines within ">" blocks, and ensuring there is a blank line before and after every table and code fence; apply these fixes consistently across the mentioned sections (the directory tree/code blocks, blockquotes, and tables) so MD040/MD028/MD058 warnings are resolved.docs/superpowers/plans/2026-05-29-exploration-domain.md (1)
1861-1863: ⚡ Quick win코드 펜스에 언어 태그를 추가해 markdownlint 경고를 없애주세요.
여러 fenced block이 언어 미지정이라 MD040 경고가 발생합니다.
Also applies to: 1883-1885, 1893-1895, 1899-1901, 1905-1907
🤖 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 `@docs/superpowers/plans/2026-05-29-exploration-domain.md` around lines 1861 - 1863, Add language tags to the fenced code blocks that currently begin with ``` to silence markdownlint MD040; specifically update the fences that contain the table rows like the one referencing `V0_0_42__add_exploration.sql` and similar blocks (those listing migration/version rows) by changing ``` to ```sql or ```text as appropriate so the code fence includes a language identifier; apply the same change to the other occurrences that list migration rows (the blocks noted after the first, and the ones around the other migration entries) so all fenced blocks have a language tag.SS-Common/src/main/java/com/elipair/spacestudyship/common/exception/InsufficientFuelException.java (1)
6-6: 💤 Low valueCustomException 패턴과의 일관성을 고려해보세요.
현재 구현은
RuntimeException을 직접 상속하고 있으나, 코딩 가이드라인에서는CustomException과ErrorCodeenum을 사용한 에러 처리를 권장합니다. 현재 구현은 추가 데이터(requiredFuel,currentFuel)를 전달하기 위한 합리적인 설계 선택이지만, 프로젝트 전체의 예외 처리 일관성을 위해 다음 옵션을 고려해볼 수 있습니다:
CustomException을 확장하여 옵셔널 데이터 필드를 지원하도록 개선InsufficientFuelException이CustomException을 상속하도록 변경현 상태로도 기능적으로 문제는 없으며, 전용
@ExceptionHandler를 통해 적절히 처리되고 있습니다.코딩 가이드라인에 따르면 CustomException과 ErrorCode enum을 사용한 에러 처리가 권장됩니다.
🤖 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 `@SS-Common/src/main/java/com/elipair/spacestudyship/common/exception/InsufficientFuelException.java` at line 6, InsufficientFuelException currently extends RuntimeException but should follow the project pattern by extending CustomException and using the ErrorCode enum; update InsufficientFuelException to extend CustomException, call super(ErrorCode.INSUFFICIENT_FUEL) (or the appropriate enum constant) from its constructors, retain the requiredFuel and currentFuel fields and their getters, and ensure constructors forward a clear message to the CustomException base (or set message via ErrorCode) so existing `@ExceptionHandler` logic continues to work; alternatively, if you prefer keeping RuntimeException, add support in CustomException to accept optional extra-data fields and adapt InsufficientFuelException to populate those, referencing the class name InsufficientFuelException, fields requiredFuel/currentFuel, and the ErrorCode enum.SS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/repository/UserExplorationRepositoryTest.java (1)
37-39: ⚡ Quick win유니크 제약 위반 시 더 구체적인 예외 타입을 검증하세요.
현재
Exception.class는 너무 광범위하여 의도하지 않은 다른 예외도 테스트를 통과시킬 수 있습니다. 유니크 제약 위반 시 발생하는DataIntegrityViolationException을 명시적으로 검증하면 테스트의 정확성이 높아집니다.♻️ 제안하는 개선안
+import org.springframework.dao.DataIntegrityViolationException; assertThatThrownBy(() -> repository.saveAndFlush(UserExploration.unlock(1L, "mars", false))) - .isInstanceOf(Exception.class); + .isInstanceOf(DataIntegrityViolationException.class);🤖 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 `@SS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/repository/UserExplorationRepositoryTest.java` around lines 37 - 39, The test currently asserts a too-broad exception when calling repository.saveAndFlush(UserExploration.unlock(1L, "mars", false))); replace the generic isInstanceOf(Exception.class) with isInstanceOf(DataIntegrityViolationException.class) to explicitly verify the unique constraint violation, and add the necessary import for org.springframework.dao.DataIntegrityViolationException in UserExplorationRepositoryTest.java; keep the assertThatThrownBy usage and target the same repository.saveAndFlush / UserExploration.unlock invocation.SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/service/ExplorationService.java (1)
107-109: ⚡ Quick win연료 소모 시 결정적 멱등 키 사용을 권장합니다.
fuelService.consume는transactionId기반 멱등성을 지원하지만, 매 호출마다UUID.randomUUID()를 전달하면 그 보호가 무력화됩니다. 현재는existsByUserIdAndNodeId선검사와 유니크 제약으로 중복 소모가 방지되나, 재시도 안전성과 의도 명확화를 위해exploration-unlock-{userId}-{nodeId}같은 결정적 키를 권장합니다. (unlockPlanet의 Line 141-143도 동일)♻️ 제안
FuelTransactionResponse fuelTx = fuelService.consume( userId, region.getRequiredFuel(), FuelReason.EXPLORATION_UNLOCK, - regionId, UUID.randomUUID().toString()); + regionId, "exploration-unlock-" + userId + "-" + regionId);🤖 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 `@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/service/ExplorationService.java` around lines 107 - 109, The current calls to fuelService.consume in ExplorationService use UUID.randomUUID() which defeats the service's transactionId-based idempotency; replace the random id with a deterministic id (e.g. "exploration-unlock-{userId}-{regionId}") when calling fuelService.consume for the unlock flow (the call using userId, region.getRequiredFuel(), FuelReason.EXPLORATION_UNLOCK, regionId, UUID.randomUUID().toString()), and apply the same change in unlockPlanet where UUID.randomUUID() is used, so transactionId is stable across retries and expresses intent.
🤖 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 `@docs/api-specs/exploration-frontend-requirements.md`:
- Line 6: Replace any local absolute paths like "/Users/..." in
docs/api-specs/exploration-frontend-requirements.md with repository-relative
paths or simple filenames so the doc is portable; specifically update references
pointing at lib/features/exploration/ (and any code blocks or examples) to use
relative paths (e.g., lib/features/exploration/ComponentName or
./lib/features/exploration/...) or just the file names, and ensure any copied
examples no longer contain user-specific absolute paths.
- Around line 204-212: The backend-spec column in the comparison table is out of
sync with the canonical contract in 05_exploration.md; update the rows "행성 구성",
"진행 게이트", "행성 연료", "지구 지역", "지역 ID 규칙", and "지구 지역 연료" in the table under
docs/api-specs/exploration-frontend-requirements.md so the "백엔드 spec 예시" values
exactly match the latest 05_exploration.md contract: remove the moon/other stale
planets if absent in the canonical spec, make the prerequisite/chain semantics
match (e.g., use `prerequisiteId` chain behavior if present), set planet fuel
numbers to the canonical values, align earth-region list/count to the canonical
regions, switch region ID format to the canonical naming scheme (name-based vs
prefix-based) and update earth-region fuel ranges to the values from
05_exploration.md.
In `@docs/superpowers/plans/2026-05-29-exploration-domain.md`:
- Line 12: Replace the hard-coded local absolute path string
"/Users/luca/workspace/Flutter_Project/space_study_ship/lib/features/exploration/data/seed/exploration_seed_data.dart"
with a repository-relative reference (e.g., a path relative to the repo root or
a docs asset link) so the document uses a portable, team-sharable path; update
the mention in the markdown so it points to the same file via repo-relative
notation (or a relative link) rather than the developer's local filesystem.
- Around line 222-227: Replace direct, auto-executing commit instructions (e.g.,
the "Step 8: Commit" block that runs `git add ...` and `git commit -m "..."`)
with a prompt that requires explicit user approval before committing; update the
text so the commit step reads something like "Prepare commit (do not run git
commit automatically) — ask user to confirm before executing `git commit`" and
apply the same change to all other commit sections referenced (the blocks at the
other occurrences listed in the comment). Locate the commit blocks by searching
for the "Step *: Commit" headings and the inline git commands (e.g., the `git
add ...` and `git commit -m` lines) and replace the imperative commit action
with a clear instruction to request user confirmation prior to running the
commit command.
- Line 17: Update the commit message example line that currently reads "커밋 형식:
`탐험 도메인 구현 : {type} : {설명} `#27``" to match the repository standard by using the
format "{issue_title} : {type} : {change_description} {issue_url}" and enforce
the allowed types (feat, fix, refactor, docs, chore, test, style) and the rule
forbidding emojis and special characters; replace the hardcoded "`#27`" style
issue marker with the full {issue_url} placeholder and ensure the example uses
the canonical order and placeholders described in the repository rule.
In `@docs/superpowers/specs/2026-05-29-exploration-domain-design.md`:
- Line 6: Remove the personal absolute path string
`/Users/luca/workspace/Flutter_Project/space_study_ship/lib/features/exploration/data/seed/exploration_seed_data.dart`
from the document and replace it with a portable reference such as a
repository-relative path (e.g.,
`lib/features/exploration/data/seed/exploration_seed_data.dart`) or a generic
placeholder (`<project-root>/lib/...`) so the spec no longer exposes local
environment details; update any surrounding text to match the new, portable path
reference.
In
`@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/dto/PlanetResponse.java`:
- Around line 33-35: The formatter assumes the LocalDateTime is UTC which can
misrepresent UserExploration.unlockedAt (set via UserExploration.unlock() using
LocalDateTime.now()); update PlanetResponse.formatUtc(LocalDateTime) to convert
the local time to an Instant using the server timezone explicitly (e.g.
ZonedDateTime.of(time, ZoneId.systemDefault()).toInstant()) before formatting
with ISO_UTC, or alternatively change UserExploration.unlock() to store an
Instant (Instant.now(ZoneOffset.UTC)) and have formatUtc accept/format an
Instant; pick one approach and apply it consistently across formatUtc,
UserExploration.unlock(), and the unlockedAt field handling.
In
`@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/entity/UserExploration.java`:
- Around line 47-55: The unlock method records unlockedAt using
LocalDateTime.now() (system default zone) while DTO.formatUtc(LocalDateTime)
treats the value as UTC, causing timezone offsets; modify UserExploration.unlock
to set unlockedAt in UTC (e.g., LocalDateTime.now(ZoneOffset.UTC) or
LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC)) so the stored unlockedAt
aligns with the DTO's UTC serialization expectations (update the unlock method
and ensure the unlockedAt field assignment uses the UTC-based LocalDateTime).
In
`@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/service/ExplorationService.java`:
- Around line 47-62: clearedByParent currently counts only regions present in
the progress/unlocked set and thus omits free regions (requiredFuel==0); update
the aggregation so free regions are treated as cleared too by changing the
filter used to build clearedByParent to include regions where
r.getRequiredFuel() == 0 in addition to unlocked.contains(r.getId()) (i.e.,
count regions where unlocked contains the id OR requiredFuel == 0), so the rest
of the planets.stream(...) logic (PlanetResponse.of, isCleared, ratio
calculations) reflects the same implicit unlocking used by getRegions.
- Around line 161-169: The isPlanetCleared method wrongly requires a
UserExploration record for every region, causing planets with free regions
(requiredFuel==0) to never be considered cleared; update isPlanetCleared to
treat a region as cleared if either the user has a UserExploration record
(userExplorationRepository → UserExploration nodeId) OR the region reports
requiredFuel==0 (check ExplorationNode.getRequiredFuel() == 0), so use
regions.stream().allMatch(r -> r.getRequiredFuel() == 0 ||
unlocked.contains(r.getId())); also review related logic in getRegions,
unlockRegion, and unlockPlanet to ensure the same implicit “free regions are
already unlocked/cleared” semantics are applied consistently.
In
`@SS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/service/ExplorationServiceTest.java`:
- Around line 218-221: The test fixture incorrectly adds a UserExploration
record for the free region "korea" (requiredFuel=0), masking the real behavior
where free regions do not have progress records and unlockRegion would throw
ALREADY_UNLOCKED; update the mocks so
nodeRepository.findByParentIdOrderBySortOrderAsc still returns region("korea",
"earth", 0, 0) but userExplorationRepository.findByUserId does NOT include
UserExploration.unlock(1L, "korea", true) (return empty or omit that region),
and adjust any related expectations/assertions so the service exercises the code
path that treats free regions as absent from the progress map (affecting
isPlanetCleared/unlockRegion behavior).
In
`@SS-Web/src/main/java/com/elipair/spacestudyship/controller/exploration/ExplorationController.java`:
- Around line 53-71: Both unlockRegion and unlockPlanet are creation actions but
currently return 200; change their responses to 201. Replace the
ResponseEntity.ok(...) uses in ExplorationController.unlockRegion and
ExplorationController.unlockPlanet with
ResponseEntity.status(HttpStatus.CREATED).body(...) (calling
explorationService.unlockRegion(...) / unlockPlanet(...) as before). If the API
spec or frontend contract remains 200, leave as-is; otherwise update
docs/api-specs/05_exploration.md and adjust the test assertion in
ExplorationControllerTest (the isOk() check) to expect CREATED.
---
Nitpick comments:
In `@docs/api-specs/05_exploration.md`:
- Around line 395-405: The fenced code block containing the SQL-like transaction
(starts with "BEGIN TRANSACTION;") is missing a language tag which triggers
markdownlint MD040; update that fenced block to include a language identifier
such as ```sql (or ```text) so the block becomes ```sql ... ``` and the linter
warning is resolved.
In `@docs/superpowers/plans/2026-05-29-exploration-domain.md`:
- Around line 1861-1863: Add language tags to the fenced code blocks that
currently begin with ``` to silence markdownlint MD040; specifically update the
fences that contain the table rows like the one referencing
`V0_0_42__add_exploration.sql` and similar blocks (those listing
migration/version rows) by changing ``` to ```sql or ```text as appropriate so
the code fence includes a language identifier; apply the same change to the
other occurrences that list migration rows (the blocks noted after the first,
and the ones around the other migration entries) so all fenced blocks have a
language tag.
In `@docs/superpowers/specs/2026-05-29-exploration-domain-design.md`:
- Around line 53-69: Fix the Markdown lint issues by adding missing fence
language tags to all code blocks (e.g., the directory tree block should be
```text or ```bash), removing extra blank lines inside any blockquotes so
there's no leading/trailing empty lines within ">" blocks, and ensuring there is
a blank line before and after every table and code fence; apply these fixes
consistently across the mentioned sections (the directory tree/code blocks,
blockquotes, and tables) so MD040/MD028/MD058 warnings are resolved.
In
`@SS-Common/src/main/java/com/elipair/spacestudyship/common/exception/InsufficientFuelException.java`:
- Line 6: InsufficientFuelException currently extends RuntimeException but
should follow the project pattern by extending CustomException and using the
ErrorCode enum; update InsufficientFuelException to extend CustomException, call
super(ErrorCode.INSUFFICIENT_FUEL) (or the appropriate enum constant) from its
constructors, retain the requiredFuel and currentFuel fields and their getters,
and ensure constructors forward a clear message to the CustomException base (or
set message via ErrorCode) so existing `@ExceptionHandler` logic continues to
work; alternatively, if you prefer keeping RuntimeException, add support in
CustomException to accept optional extra-data fields and adapt
InsufficientFuelException to populate those, referencing the class name
InsufficientFuelException, fields requiredFuel/currentFuel, and the ErrorCode
enum.
In
`@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/service/ExplorationService.java`:
- Around line 107-109: The current calls to fuelService.consume in
ExplorationService use UUID.randomUUID() which defeats the service's
transactionId-based idempotency; replace the random id with a deterministic id
(e.g. "exploration-unlock-{userId}-{regionId}") when calling fuelService.consume
for the unlock flow (the call using userId, region.getRequiredFuel(),
FuelReason.EXPLORATION_UNLOCK, regionId, UUID.randomUUID().toString()), and
apply the same change in unlockPlanet where UUID.randomUUID() is used, so
transactionId is stable across retries and expresses intent.
In
`@SS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/repository/UserExplorationRepositoryTest.java`:
- Around line 37-39: The test currently asserts a too-broad exception when
calling repository.saveAndFlush(UserExploration.unlock(1L, "mars", false)));
replace the generic isInstanceOf(Exception.class) with
isInstanceOf(DataIntegrityViolationException.class) to explicitly verify the
unique constraint violation, and add the necessary import for
org.springframework.dao.DataIntegrityViolationException in
UserExplorationRepositoryTest.java; keep the assertThatThrownBy usage and target
the same repository.saveAndFlush / UserExploration.unlock invocation.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: bc9085e1-d694-4d5f-9b4e-4bd01f556ac2
📒 Files selected for processing (34)
CLAUDE.mdSS-Common/build.gradleSS-Common/src/main/java/com/elipair/spacestudyship/common/exception/ErrorCode.javaSS-Common/src/main/java/com/elipair/spacestudyship/common/exception/ErrorResponse.javaSS-Common/src/main/java/com/elipair/spacestudyship/common/exception/GlobalExceptionHandler.javaSS-Common/src/main/java/com/elipair/spacestudyship/common/exception/InsufficientFuelException.javaSS-Common/src/test/java/com/elipair/spacestudyship/common/exception/ErrorResponseTest.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/constant/NodeType.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/constant/NodeTypeConverter.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/dto/PlanetResponse.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/dto/PlanetUnlockResponse.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/dto/ProgressDto.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/dto/RegionResponse.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/dto/RegionUnlockResponse.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/dto/UnlockedNodeDto.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/entity/ExplorationNode.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/entity/UserExploration.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/repository/ExplorationNodeRepository.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/repository/UserExplorationRepository.javaSS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/service/ExplorationService.javaSS-Study/src/test/java/com/elipair/spacestudyship/study/StudyTestApplication.javaSS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/entity/ExplorationNodeTest.javaSS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/entity/UserExplorationTest.javaSS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/repository/ExplorationNodeRepositoryTest.javaSS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/repository/UserExplorationRepositoryTest.javaSS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/service/ExplorationServiceTest.javaSS-Web/src/main/java/com/elipair/spacestudyship/controller/exploration/ExplorationController.javaSS-Web/src/main/resources/application.ymlSS-Web/src/main/resources/db/migration/V0_0_42__add_exploration.sqlSS-Web/src/test/java/com/elipair/spacestudyship/controller/exploration/ExplorationControllerTest.javadocs/api-specs/05_exploration.mddocs/api-specs/exploration-frontend-requirements.mddocs/superpowers/plans/2026-05-29-exploration-domain.mddocs/superpowers/specs/2026-05-29-exploration-domain-design.md
| > **작성:** 2026-05-29 | ||
| > **대상 기능:** Exploration (행성/지역 탐험) | ||
| > **성격:** Flutter(프론트)가 백엔드에 요구하는 API 계약 명세. 백엔드 `docs/api-specs/05_exploration.md`와 대조·정합을 맞추기 위한 문서. | ||
| > **관련 코드:** `lib/features/exploration/` |
There was a problem hiding this comment.
로컬 절대 경로는 저장소 상대 경로로 바꿔주세요.
/Users/... 형태 경로는 문서 이식성과 공유성을 떨어뜨립니다. 저장소 기준 상대 경로나 파일명 기준 참조로 변경하는 게 안전합니다.
🤖 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 `@docs/api-specs/exploration-frontend-requirements.md` at line 6, Replace any
local absolute paths like "/Users/..." in
docs/api-specs/exploration-frontend-requirements.md with repository-relative
paths or simple filenames so the doc is portable; specifically update references
pointing at lib/features/exploration/ (and any code blocks or examples) to use
relative paths (e.g., lib/features/exploration/ComponentName or
./lib/features/exploration/...) or just the file names, and ensure any copied
examples no longer contain user-specific absolute paths.
| | 항목 | 프론트 시드 (게스트 로컬) | 백엔드 spec 예시 | | ||
| |------|--------------------------|------------------| | ||
| | 행성 구성 | 지구·수성·금성·화성·목성·토성·천왕성·해왕성 (8개, **달 없음**) | 지구·달·화성 (예시 3개) | | ||
| | 진행 게이트 | **선행조건 없음** (연료만 있으면 해금) | 선행 체인 (지구→달→화성, `prerequisiteId`) | | ||
| | 행성 연료 | earth 0 / mercury 3 / venus 5 / mars 10 / jupiter 20 / saturn 30 / uranus 45 / neptune 60 | earth 0 / moon 8 / mars 12 | | ||
| | 지구 지역 | 12개 (korea, japan, thailand, china, india, uk, france, canada, usa, brazil, australia, egypt) | 2개 예시 (대한민국, 일본) | | ||
| | 지역 ID 규칙 | `korea`, `japan`, `usa` (이름 기반) | `region-kr`, `region-jp` (prefix 기반) | | ||
| | 지구 지역 연료 | 0~3 | 4~6 | | ||
|
|
There was a problem hiding this comment.
백엔드 스펙 비교표가 현재 스펙과 상충합니다.
이 표의 백엔드 spec 예시 값(달 포함, region-kr, 연료 수치 등)은 같은 PR에서 갱신된 docs/api-specs/05_exploration.md와 충돌합니다. 최신 계약 기준으로 표를 맞추지 않으면 프론트 구현 판단이 뒤틀릴 수 있습니다.
🤖 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 `@docs/api-specs/exploration-frontend-requirements.md` around lines 204 - 212,
The backend-spec column in the comparison table is out of sync with the
canonical contract in 05_exploration.md; update the rows "행성 구성", "진행 게이트", "행성
연료", "지구 지역", "지역 ID 규칙", and "지구 지역 연료" in the table under
docs/api-specs/exploration-frontend-requirements.md so the "백엔드 spec 예시" values
exactly match the latest 05_exploration.md contract: remove the moon/other stale
planets if absent in the canonical spec, make the prerequisite/chain semantics
match (e.g., use `prerequisiteId` chain behavior if present), set planet fuel
numbers to the canonical values, align earth-region list/count to the canonical
regions, switch region ID format to the canonical naming scheme (name-based vs
prefix-based) and update earth-region fuel ranges to the values from
05_exploration.md.
| **Tech Stack:** Java 21, Spring Boot 4, Spring Data JPA, Lombok, JUnit5+Mockito+AssertJ, Testcontainers(Postgres), Flyway, springdoc. | ||
|
|
||
| **Spec:** `docs/superpowers/specs/2026-05-29-exploration-domain-design.md` | ||
| **프론트 시드 원본:** `/Users/luca/workspace/Flutter_Project/space_study_ship/lib/features/exploration/data/seed/exploration_seed_data.dart` |
There was a problem hiding this comment.
로컬 절대 경로 대신 저장소 기준 참조를 사용해주세요.
개인 환경 경로(/Users/...)는 문서 재사용성과 팀 공용성에 불리합니다.
🤖 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 `@docs/superpowers/plans/2026-05-29-exploration-domain.md` at line 12, Replace
the hard-coded local absolute path string
"/Users/luca/workspace/Flutter_Project/space_study_ship/lib/features/exploration/data/seed/exploration_seed_data.dart"
with a repository-relative reference (e.g., a path relative to the repo root or
a docs asset link) so the document uses a portable, team-sharable path; update
the mention in the markdown so it points to the same file via repo-relative
notation (or a relative link) rather than the developer's local filesystem.
| **공통 규칙:** | ||
| - 테스트: `./gradlew :SS-Study:test`, `./gradlew :SS-Web:test`, `./gradlew :SS-Common:test`. 단일: `--tests "FQCN"`. | ||
| - 테스트 환경 = Testcontainers + `ddl-auto=create-drop` (엔티티가 스키마 생성, Flyway 비활성). `members` FK는 엔티티에 매핑하지 않음(마이그레이션에만 존재). | ||
| - 커밋 형식: `탐험 도메인 구현 : {type} : {설명} #27`. 이슈번호 #27. **이모지 금지. Co-Authored-By 금지.** |
There was a problem hiding this comment.
문서의 커밋 메시지 규칙이 저장소 규칙과 다릅니다.
여기 적힌 포맷(#27)은 학습된 저장소 규칙({issue_title} : {type} : {change_description} {issue_url})과 불일치합니다. 계획 문구를 저장소 표준으로 맞춰주세요.
Based on learnings: "All commit messages must follow the format: {issue_title} : {type} : {change_description} {issue_url}, where type is one of: feat, fix, refactor, docs, chore, test, style. Emoji and special characters are forbidden in commit messages."
🤖 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 `@docs/superpowers/plans/2026-05-29-exploration-domain.md` at line 17, Update
the commit message example line that currently reads "커밋 형식: `탐험 도메인 구현 : {type}
: {설명} `#27``" to match the repository standard by using the format "{issue_title}
: {type} : {change_description} {issue_url}" and enforce the allowed types
(feat, fix, refactor, docs, chore, test, style) and the rule forbidding emojis
and special characters; replace the hardcoded "`#27`" style issue marker with the
full {issue_url} placeholder and ensure the example uses the canonical order and
placeholders described in the repository rule.
| - [ ] **Step 8: Commit** | ||
|
|
||
| ```bash | ||
| git add SS-Common/src/main/java/com/elipair/spacestudyship/common/exception/ SS-Common/src/test/java/com/elipair/spacestudyship/common/exception/ErrorResponseTest.java | ||
| git commit -m "탐험 도메인 구현 : feat : 탐험 ErrorCode 5종 + INSUFFICIENT_FUEL 응답 보강 #27" | ||
| ``` |
There was a problem hiding this comment.
명시적 사용자 요청 없이 커밋을 수행하도록 유도하는 단계는 정책과 충돌합니다.
여러 Task에서 즉시 git commit 실행을 지시하고 있어, 사용자 확인 선행 정책과 충돌합니다. 커밋 단계는 “사용자 승인 후 실행”으로 바꿔주세요.
Based on learnings: "Never commit without explicit user request. Claude must always ask for confirmation before creating any commits."
Also applies to: 287-290, 408-411, 522-525, 709-711, 898-900, 1121-1123, 1324-1326, 1474-1476, 1751-1753, 1868-1870, 1927-1929
🤖 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 `@docs/superpowers/plans/2026-05-29-exploration-domain.md` around lines 222 -
227, Replace direct, auto-executing commit instructions (e.g., the "Step 8:
Commit" block that runs `git add ...` and `git commit -m "..."`) with a prompt
that requires explicit user approval before committing; update the text so the
commit step reads something like "Prepare commit (do not run git commit
automatically) — ask user to confirm before executing `git commit`" and apply
the same change to all other commit sections referenced (the blocks at the other
occurrences listed in the comment). Locate the commit blocks by searching for
the "Step *: Commit" headings and the inline git commands (e.g., the `git add
...` and `git commit -m` lines) and replace the imperative commit action with a
clear instruction to request user confirmation prior to running the commit
command.
| public static UserExploration unlock(Long userId, String nodeId, boolean cleared) { | ||
| return UserExploration.builder() | ||
| .userId(userId) | ||
| .nodeId(nodeId) | ||
| .isUnlocked(true) | ||
| .isCleared(cleared) | ||
| .unlockedAt(LocalDateTime.now()) | ||
| .build(); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# formatUtc 구현 및 unlockedAt 직렬화 방식 확인
rg -nP -C5 '\bformatUtc\b' --type=java
rg -nP -C3 'unlockedAt' --type=java -g '**/dto/**'Repository: SpaceStudyShip/SpaceStudyShip-BE
Length of output: 18302
unlockedAt 저장/직렬화 시간대 불일치 가능성
DTO의 formatUtc(LocalDateTime)가 ISO_INSTANT에 대해 time.toInstant(ZoneOffset.UTC)로 변환해 “UTC로 가정”하는데, unlock()은 LocalDateTime.now()(시스템 기본 타임존 기준 벽시계)를 그대로 기록하고 있어 JVM 타임존이 UTC가 아닐 때 응답 시각이 오프셋될 수 있습니다.
🛠️ 제안 수정
+import java.time.ZoneOffset;
+
public static UserExploration unlock(Long userId, String nodeId, boolean cleared) {
return UserExploration.builder()
.userId(userId)
.nodeId(nodeId)
.isUnlocked(true)
.isCleared(cleared)
- .unlockedAt(LocalDateTime.now())
+ .unlockedAt(LocalDateTime.now(ZoneOffset.UTC))
.build();
}📝 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.
| public static UserExploration unlock(Long userId, String nodeId, boolean cleared) { | |
| return UserExploration.builder() | |
| .userId(userId) | |
| .nodeId(nodeId) | |
| .isUnlocked(true) | |
| .isCleared(cleared) | |
| .unlockedAt(LocalDateTime.now()) | |
| .build(); | |
| } | |
| import java.time.ZoneOffset; | |
| public static UserExploration unlock(Long userId, String nodeId, boolean cleared) { | |
| return UserExploration.builder() | |
| .userId(userId) | |
| .nodeId(nodeId) | |
| .isUnlocked(true) | |
| .isCleared(cleared) | |
| .unlockedAt(LocalDateTime.now(ZoneOffset.UTC)) | |
| .build(); | |
| } |
🤖 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
`@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/entity/UserExploration.java`
around lines 47 - 55, The unlock method records unlockedAt using
LocalDateTime.now() (system default zone) while DTO.formatUtc(LocalDateTime)
treats the value as UTC, causing timezone offsets; modify UserExploration.unlock
to set unlockedAt in UTC (e.g., LocalDateTime.now(ZoneOffset.UTC) or
LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC)) so the stored unlockedAt
aligns with the DTO's UTC serialization expectations (update the unlock method
and ensure the unlockedAt field assignment uses the UTC-based LocalDateTime).
| Map<String, Long> totalByParent = regions.stream() | ||
| .collect(Collectors.groupingBy(ExplorationNode::getParentId, Collectors.counting())); | ||
| Map<String, Long> clearedByParent = regions.stream() | ||
| .filter(r -> unlocked.contains(r.getId())) | ||
| .collect(Collectors.groupingBy(ExplorationNode::getParentId, Collectors.counting())); | ||
|
|
||
| return planets.stream().map(p -> { | ||
| int total = totalByParent.getOrDefault(p.getId(), 0L).intValue(); | ||
| int cleared = clearedByParent.getOrDefault(p.getId(), 0L).intValue(); | ||
| boolean isUnlocked = p.getRequiredFuel() == 0 || unlocked.contains(p.getId()); | ||
| boolean isCleared = total > 0 && cleared == total; | ||
| double ratio = total == 0 ? 0.0 : (double) cleared / total; | ||
| LocalDateTime unlockedAt = progress.containsKey(p.getId()) | ||
| ? progress.get(p.getId()).getUnlockedAt() : null; | ||
| return PlanetResponse.of(p, isUnlocked, isCleared, cleared, total, ratio, unlockedAt); | ||
| }).toList(); |
There was a problem hiding this comment.
진행도 집계에서 무료 지역이 누락되어 getRegions와 불일치합니다.
clearedByParent(Line 49-51)는 진행도 맵에 존재하는 지역만 카운트하지만, requiredFuel==0 지역은 진행도 레코드가 생성되지 않습니다(isPlanetCleared에 남긴 동일 원인). 따라서 무료 지역을 포함한 행성은 모든 지역이 해금된 상태여도 cleared/progressRatio/isCleared(Line 55-58)가 getRegions의 암묵 해금 결과와 어긋나게 됩니다. 무료 지역도 cleared로 포함하도록 보정해 주세요.
🤖 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
`@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/service/ExplorationService.java`
around lines 47 - 62, clearedByParent currently counts only regions present in
the progress/unlocked set and thus omits free regions (requiredFuel==0); update
the aggregation so free regions are treated as cleared too by changing the
filter used to build clearedByParent to include regions where
r.getRequiredFuel() == 0 in addition to unlocked.contains(r.getId()) (i.e.,
count regions where unlocked contains the id OR requiredFuel == 0), so the rest
of the planets.stream(...) logic (PlanetResponse.of, isCleared, ratio
calculations) reflects the same implicit unlocking used by getRegions.
| private boolean isPlanetCleared(Long userId, String planetId) { | ||
| List<ExplorationNode> regions = nodeRepository.findByParentIdOrderBySortOrderAsc(planetId); | ||
| if (regions.isEmpty()) { | ||
| return false; | ||
| } | ||
| Set<String> unlocked = userExplorationRepository.findByUserId(userId).stream() | ||
| .map(UserExploration::getNodeId).collect(Collectors.toSet()); | ||
| return regions.stream().allMatch(r -> unlocked.contains(r.getId())); | ||
| } |
There was a problem hiding this comment.
무료 지역(requiredFuel==0)으로 인해 행성 클리어가 영구히 false가 됩니다.
unlocked 집합은 진행도 레코드(UserExploration)가 있는 노드만 포함합니다. 그러나 requiredFuel==0 지역은 unlockRegion이 항상 ALREADY_UNLOCKED을 던지므로(Line 100-103) 진행도 레코드가 절대 생성되지 않습니다. 결과적으로 무료 지역을 하나라도 가진 행성은 allMatch가 결코 true가 되지 못해, unlockRegion의 planetCleared 판정과 unlockPlanet의 선행 조건 게이트(Line 134-137)가 영구적으로 차단됩니다.
getRegions(Line 75,77)가 무료 지역을 암묵 해금/클리어로 취급하는 것과도 모순됩니다. 무료 지역을 해금/클리어로 간주하도록 보정이 필요합니다.
🐛 보정 예시
private boolean isPlanetCleared(Long userId, String planetId) {
List<ExplorationNode> regions = nodeRepository.findByParentIdOrderBySortOrderAsc(planetId);
if (regions.isEmpty()) {
return false;
}
Set<String> unlocked = userExplorationRepository.findByUserId(userId).stream()
.map(UserExploration::getNodeId).collect(Collectors.toSet());
- return regions.stream().allMatch(r -> unlocked.contains(r.getId()));
+ return regions.stream()
+ .allMatch(r -> r.getRequiredFuel() == 0 || unlocked.contains(r.getId()));
}📝 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.
| private boolean isPlanetCleared(Long userId, String planetId) { | |
| List<ExplorationNode> regions = nodeRepository.findByParentIdOrderBySortOrderAsc(planetId); | |
| if (regions.isEmpty()) { | |
| return false; | |
| } | |
| Set<String> unlocked = userExplorationRepository.findByUserId(userId).stream() | |
| .map(UserExploration::getNodeId).collect(Collectors.toSet()); | |
| return regions.stream().allMatch(r -> unlocked.contains(r.getId())); | |
| } | |
| private boolean isPlanetCleared(Long userId, String planetId) { | |
| List<ExplorationNode> regions = nodeRepository.findByParentIdOrderBySortOrderAsc(planetId); | |
| if (regions.isEmpty()) { | |
| return false; | |
| } | |
| Set<String> unlocked = userExplorationRepository.findByUserId(userId).stream() | |
| .map(UserExploration::getNodeId).collect(Collectors.toSet()); | |
| return regions.stream() | |
| .allMatch(r -> r.getRequiredFuel() == 0 || unlocked.contains(r.getId())); | |
| } |
🤖 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
`@SS-Study/src/main/java/com/elipair/spacestudyship/study/exploration/service/ExplorationService.java`
around lines 161 - 169, The isPlanetCleared method wrongly requires a
UserExploration record for every region, causing planets with free regions
(requiredFuel==0) to never be considered cleared; update isPlanetCleared to
treat a region as cleared if either the user has a UserExploration record
(userExplorationRepository → UserExploration nodeId) OR the region reports
requiredFuel==0 (check ExplorationNode.getRequiredFuel() == 0), so use
regions.stream().allMatch(r -> r.getRequiredFuel() == 0 ||
unlocked.contains(r.getId())); also review related logic in getRegions,
unlockRegion, and unlockPlanet to ensure the same implicit “free regions are
already unlocked/cleared” semantics are applied consistently.
| given(nodeRepository.findByParentIdOrderBySortOrderAsc("earth")) | ||
| .willReturn(List.of(region("korea", "earth", 0, 0))); | ||
| given(userExplorationRepository.findByUserId(1L)) | ||
| .willReturn(List.of(UserExploration.unlock(1L, "korea", true))); |
There was a problem hiding this comment.
테스트 픽스처가 무료 지역 관련 결함을 가립니다.
여기(및 Line 136-138)에서 korea(requiredFuel=0)를 진행도 맵에 직접 넣어 선행 클리어/planetCleared를 충족시킵니다. 그러나 실제로는 무료 지역에 진행도 레코드가 생성되지 않으므로(unlockRegion이 ALREADY_UNLOCKED을 던짐), isPlanetCleared가 무료 지역을 클리어로 인식하지 못합니다. 서비스 로직 보정 후, 무료 지역이 진행도 맵에 없는 실제 상태를 반영하도록 테스트를 보완해 주세요.
🤖 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
`@SS-Study/src/test/java/com/elipair/spacestudyship/study/exploration/service/ExplorationServiceTest.java`
around lines 218 - 221, The test fixture incorrectly adds a UserExploration
record for the free region "korea" (requiredFuel=0), masking the real behavior
where free regions do not have progress records and unlockRegion would throw
ALREADY_UNLOCKED; update the mocks so
nodeRepository.findByParentIdOrderBySortOrderAsc still returns region("korea",
"earth", 0, 0) but userExplorationRepository.findByUserId does NOT include
UserExploration.unlock(1L, "korea", true) (return empty or omit that region),
and adjust any related expectations/assertions so the service exercises the code
path that treats free regions as absent from the progress map (affecting
isPlanetCleared/unlockRegion behavior).
| @PostMapping("/regions/{regionId}/unlock") | ||
| public ResponseEntity<RegionUnlockResponse> unlockRegion( | ||
| @AuthMember LoginMember loginMember, | ||
| @PathVariable String regionId) { | ||
| return ResponseEntity.ok(explorationService.unlockRegion(loginMember.memberId(), regionId)); | ||
| } | ||
|
|
||
| @Operation(summary = "행성 해금", | ||
| description = """ | ||
| 연료를 소비하여 행성을 해금합니다. 선행 행성을 클리어해야 해금할 수 있습니다. | ||
|
|
||
| 에러: 400 INSUFFICIENT_FUEL(requiredFuel/currentFuel 동봉) / ALREADY_UNLOCKED / PREREQUISITE_NOT_CLEARED, 404 PLANET_NOT_FOUND | ||
| """) | ||
| @PostMapping("/planets/{planetId}/unlock") | ||
| public ResponseEntity<PlanetUnlockResponse> unlockPlanet( | ||
| @AuthMember LoginMember loginMember, | ||
| @PathVariable String planetId) { | ||
| return ResponseEntity.ok(explorationService.unlockPlanet(loginMember.memberId(), planetId)); | ||
| } |
There was a problem hiding this comment.
해금 엔드포인트는 HttpStatus.CREATED(201) 반환을 검토하세요.
unlockRegion/unlockPlanet은 새 UserExploration 진행 레코드를 영속화하는 생성 동작입니다. 현재는 ResponseEntity.ok(200)로 응답합니다. 생성 엔드포인트는 201을 반환하도록 규정되어 있어 검토가 필요합니다. 단, docs/api-specs/05_exploration.md의 프론트엔드 계약이 200으로 고정되어 있다면 계약을 우선하고, 변경 시 ExplorationControllerTest의 isOk() 단언도 함께 갱신해야 합니다.
As per coding guidelines: "For creation endpoints (new member, etc.), use HttpStatus.CREATED."
♻️ 제안 변경 (계약이 201을 허용하는 경우)
- return ResponseEntity.ok(explorationService.unlockRegion(loginMember.memberId(), regionId));
+ return ResponseEntity.status(HttpStatus.CREATED)
+ .body(explorationService.unlockRegion(loginMember.memberId(), regionId));- return ResponseEntity.ok(explorationService.unlockPlanet(loginMember.memberId(), planetId));
+ return ResponseEntity.status(HttpStatus.CREATED)
+ .body(explorationService.unlockPlanet(loginMember.memberId(), planetId));📝 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.
| @PostMapping("/regions/{regionId}/unlock") | |
| public ResponseEntity<RegionUnlockResponse> unlockRegion( | |
| @AuthMember LoginMember loginMember, | |
| @PathVariable String regionId) { | |
| return ResponseEntity.ok(explorationService.unlockRegion(loginMember.memberId(), regionId)); | |
| } | |
| @Operation(summary = "행성 해금", | |
| description = """ | |
| 연료를 소비하여 행성을 해금합니다. 선행 행성을 클리어해야 해금할 수 있습니다. | |
| 에러: 400 INSUFFICIENT_FUEL(requiredFuel/currentFuel 동봉) / ALREADY_UNLOCKED / PREREQUISITE_NOT_CLEARED, 404 PLANET_NOT_FOUND | |
| """) | |
| @PostMapping("/planets/{planetId}/unlock") | |
| public ResponseEntity<PlanetUnlockResponse> unlockPlanet( | |
| @AuthMember LoginMember loginMember, | |
| @PathVariable String planetId) { | |
| return ResponseEntity.ok(explorationService.unlockPlanet(loginMember.memberId(), planetId)); | |
| } | |
| `@PostMapping`("/regions/{regionId}/unlock") | |
| public ResponseEntity<RegionUnlockResponse> unlockRegion( | |
| `@AuthMember` LoginMember loginMember, | |
| `@PathVariable` String regionId) { | |
| return ResponseEntity.status(HttpStatus.CREATED) | |
| .body(explorationService.unlockRegion(loginMember.memberId(), regionId)); | |
| } | |
| `@Operation`(summary = "행성 해금", | |
| description = """ | |
| 연료를 소비하여 행성을 해금합니다. 선행 행성을 클리어해야 해금할 수 있습니다. | |
| 에러: 400 INSUFFICIENT_FUEL(requiredFuel/currentFuel 동봉) / ALREADY_UNLOCKED / PREREQUISITE_NOT_CLEARED, 404 PLANET_NOT_FOUND | |
| """) | |
| `@PostMapping`("/planets/{planetId}/unlock") | |
| public ResponseEntity<PlanetUnlockResponse> unlockPlanet( | |
| `@AuthMember` LoginMember loginMember, | |
| `@PathVariable` String planetId) { | |
| return ResponseEntity.status(HttpStatus.CREATED) | |
| .body(explorationService.unlockPlanet(loginMember.memberId(), planetId)); | |
| } |
🤖 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
`@SS-Web/src/main/java/com/elipair/spacestudyship/controller/exploration/ExplorationController.java`
around lines 53 - 71, Both unlockRegion and unlockPlanet are creation actions
but currently return 200; change their responses to 201. Replace the
ResponseEntity.ok(...) uses in ExplorationController.unlockRegion and
ExplorationController.unlockPlanet with
ResponseEntity.status(HttpStatus.CREATED).body(...) (calling
explorationService.unlockRegion(...) / unlockPlanet(...) as before). If the API
spec or frontend contract remains 200, leave as-is; otherwise update
docs/api-specs/05_exploration.md and adjust the test assertion in
ExplorationControllerTest (the isOk() check) to expect CREATED.
🗑️ Preview 환경 삭제 완료!
다시 배포하려면: 🤖 이 댓글은 PR/Issue Preview 시스템에 의해 자동 생성되었습니다. |
✨ 변경 사항
✅ 테스트
Summary by CodeRabbit
릴리스 노트