Skip to content

[ docs ] : 아키텍처 준수 검증 스킬(architecture-skill) 추가#32

Open
JayongLee wants to merge 1 commit into
refactor/architecturefrom
docs/architecture-skill
Open

[ docs ] : 아키텍처 준수 검증 스킬(architecture-skill) 추가#32
JayongLee wants to merge 1 commit into
refactor/architecturefrom
docs/architecture-skill

Conversation

@JayongLee

@JayongLee JayongLee commented Jul 13, 2026

Copy link
Copy Markdown
Member

PR

PR 요약

이 프로젝트는 Controller → Usecase → Service → Repository 계층 방향과 "트랜잭션 경계는 Usecase가 소유"라는 규칙, 그리고 도메인별 DTO·에러 컨벤션을 따르고 있습니다. 하지만 이 규칙들이 이후 PR에서도 지켜지는지 매번 사람이 눈으로 확인하기는 번거롭습니다. 이 PR은 변경분(PR diff)이 위 아키텍처 규칙을 준수하는지 정적 분석으로 검증하는 architecture-skill을 추가합니다.

변경된 점

1. architecture-skill 추가 (.claude/skills/architecture-skill, .agents/skills/architecture-skill)

  • 배경/이유: 계층 의존 방향, Usecase 트랜잭션 경계, DTO/네이밍/에러 컨벤션이 리팩토링 이후에도 유지되는지 사람이 매번 검증하기 어렵습니다.
  • 변경 내용: git diff main...HEAD로 변경된 *.kt 파일을 수집해, import 블록 파싱과 grep으로 아래 규칙군을 정적 검증하는 스킬을 추가했습니다.
    • A. 레이어 의존 방향: controller→repository 직접 의존, service→타 도메인 service/repository 의존, 역방향 의존 탐지
    • B. 패키지/네이밍: 클래스 suffix와 패키지 레이어 일치, *Request/*Response DTO 위치 검증
    • C. 컨벤션: raw 예외 금지, @Entity의 BaseEntity 상속, service의 JPA 인프라 주입 금지 등
    • D. 진입점·트랜잭션 경계: controller의 usecase 경유, service의 @Transactional 잔존 금지, usecase 진입 메서드의 트랜잭션 경계 소유
  • 위반을 규칙 ID별로 리포트하고, 기계적으로 고칠 수 있는 항목(BaseEntity 상속 추가, service의 @Transactional 제거 등)은 일괄 자동 수정을 제안합니다. Claude(.claude/)와 Codex(.agents/) 양쪽에 동일하게 배치했습니다.

2. 스킬 지원을 위한 설정 정리 (AGENTS.md, .gitignore)

  • 변경 내용: AGENTS.md에 스킬 관련 안내를 추가하고, .gitignore를 함께 정리했습니다.

Summary by CodeRabbit

  • New Features

    • Added structured session lifecycle, lesson, reward, history, onboarding, profile, and home experiences.
    • Added dedicated request and response formats with improved API validation and documentation.
    • Added hint-note content retrieval and clearer not-found error handling.
  • Refactor

    • Reorganized service responsibilities and routing through use cases.
    • Improved separation of domain-specific API models.
  • Documentation

    • Added architecture verification guidance, rules, and report templates.

@JayongLee JayongLee self-assigned this Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR adds architecture verification skills and rules, reorganizes DTOs by domain and request/response role, splits session and user orchestration into usecases and focused services, updates transactional boundaries, and adjusts controllers, error handling, and tests accordingly.

Architecture and contracts

Layer / File(s) Summary
Architecture verification workflow
.agents/skills/architecture-skill/*, .claude/skills/architecture-skill/*, AGENTS.md, .gitignore
Adds static architecture-checking workflows, rule catalogs, report templates, auto-fix guidance, limitations, and related repository guidance.
Domain DTO separation
src/main/kotlin/.../dto/**
Moves shared payloads into domain-specific request and response DTO packages and adds hint-note error handling.
Curriculum lesson retrieval
src/main/kotlin/.../curriculum/*, src/test/kotlin/.../curriculum/*
Adds lesson topic, question, hint-note retrieval, mapping, composed lesson content, and supporting test wiring.

Session usecase restructuring

Layer / File(s) Summary
Session services
src/main/kotlin/.../session/service/*
Splits lifecycle, query, reward, completion, and history responsibilities into focused services.
Session usecase entrypoints
src/main/kotlin/.../session/usecase/*
Adds transactional usecases for lifecycle, lesson, completion, reward, and history flows.
Session controller delegation
src/main/kotlin/.../session/controller/*
Routes session endpoints through the new usecases.
Session validation
src/test/kotlin/.../session/*
Updates lifecycle tests for the new service boundaries and signatures.

User and onboarding usecases

Layer / File(s) Summary
User services and usecases
src/main/kotlin/.../user/service/*, src/main/kotlin/.../user/usecase/*
Separates profile, home, settings, and onboarding responsibilities and moves transaction ownership to usecases.
User controller delegation
src/main/kotlin/.../user/controller/*
Routes user and onboarding endpoints through usecases.
User and notification validation
src/test/kotlin/.../user/*, src/test/kotlin/.../notification/*
Updates DTO imports, profile/home assertions, integration coverage, and adds home usecase tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SessionController
  participant SessionLessonUsecase
  participant SessionQueryService
  participant CurriculumService
  Client->>SessionController: Request lesson or reaction
  SessionController->>SessionLessonUsecase: getLesson or getReaction
  SessionLessonUsecase->>SessionQueryService: Load started session and snapshot
  SessionLessonUsecase->>CurriculumService: Load lesson content by phase
  CurriculumService-->>SessionLessonUsecase: Return question and hint note
  SessionLessonUsecase-->>SessionController: Return session lesson response
  SessionController-->>Client: HTTP response
Loading

Possibly related PRs

Suggested reviewers: tlarbals824

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding an architecture compliance verification skill and related docs.
✨ 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 docs/architecture-skill

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@JayongLee
JayongLee changed the base branch from main to refactor/architecture July 13, 2026 04:52
@JayongLee
JayongLee requested a review from tlarbals824 July 13, 2026 04:52

@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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt (2)

21-27: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Move cross-domain reward updates behind domain services

UserProfileRepository, UserCurriculumRepository, and BadgeLevelRepository live outside the session domain, so SessionCompletionService is now handling user/gamification mutations directly. Keep CoinLedgerEntryRepository here, but move the profile/curriculum progress update and badge selection behind the owning service(s) instead of injecting those repositories into session.

🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`
around lines 21 - 27, Remove direct UserProfileRepository,
UserCurriculumRepository, and BadgeLevelRepository dependencies from
SessionCompletionService, and delegate profile/curriculum progress updates and
badge selection to the owning domain service(s). Keep CoinLedgerEntryRepository
in the session service and preserve the existing completion flow through
service-level operations.

79-88: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the session reward path atomic
existsBySessionId() is still a check-then-act gap here, and the completion flow has no transaction boundary. Two concurrent complete() calls can both apply the profile reward before any later session conflict, so one session can still mint coins twice. Add an atomic guard at the persistence layer and commit the whole flow as one unit.

🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`
around lines 79 - 88, The session reward logic in complete() is vulnerable to
concurrent duplicate rewards because existsBySessionId() and save() are not
atomic and the flow lacks a transaction boundary. Add a persistence-level
uniqueness/atomic guard for the session reward, make complete() transactional so
profile updates and CoinLedgerEntry persistence commit as one unit, and handle
duplicate attempts without awarding coins twice.
src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt (1)

126-126: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add the pending-reward getStatus case to SessionQueryServiceTest.
The 미수령 보상 세션 coverage was removed from SessionServiceTest, and there’s no matching test in SessionQueryServiceTest. Add the equivalent assertion there to keep getStatus covered.

🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt`
at line 126, Extend SessionQueryServiceTest with the pending-reward (“미수령 보상
세션”) getStatus scenario removed from SessionServiceTest. Recreate the equivalent
setup and assertion for the pending-reward session, ensuring the test verifies
the expected getStatus result.
🧹 Nitpick comments (5)
.agents/skills/architecture-skill/SKILL.md (1)

33-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider specifying a language for the fenced code block.

The code block starting at line 33 has no language specifier, triggering markdownlint MD040. Adding text as the language keeps it informative for syntax highlighters.

♻️ Optional fix

`
- +text
.../{domain}/controller/X.kt → (domain, controller)

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/skills/architecture-skill/SKILL.md around lines 33 - 41, Update the
fenced code block containing the architecture path mappings in SKILL.md to
specify the text language, changing the opening fence to use text while
preserving its contents.


</details>

<!-- cr-comment:v1:d72c9eca2f05b6e52c5900c4 -->

_Source: Linters/SAST tools_

</blockquote></details>
<details>
<summary>.agents/skills/architecture-skill/report-template.md (1)</summary><blockquote>

`5-5`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_

**Consider specifying a language for the fenced code block.**

The code block starting at line 5 has no language specifier (markdownlint MD040). Since it contains Markdown template content, `markdown` would be the appropriate language.





<details>
<summary>♻️ Optional fix</summary>

```diff
-```
+```markdown
 ## 🏛 아키텍처 검증 리포트
🤖 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 @.agents/skills/architecture-skill/report-template.md at line 5, Update the
fenced code block in the report template’s Markdown content to include the
`markdown` language specifier, preserving the existing template content
unchanged.

Source: Linters/SAST tools

src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt (1)

18-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the from() mapping logic to a dedicated mapper.

RewardResponse.from() imports entities from three domains (session, user, gamification), coupling a gamification DTO to session and user entities. The project already uses a separate HintNoteMapper pattern in the curriculum domain. Moving this mapping to a dedicated mapper (e.g., RewardMapper in the session or gamification service layer) would keep the DTO pure and centralize cross-domain coordination where it's architecturally appropriate.

This is not blocking — the factory pattern is idiomatic Kotlin and the logic itself is correct.

🤖 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/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt`
around lines 18 - 31, Extract the mapping logic from RewardResponse.from into a
dedicated RewardMapper, following the existing HintNoteMapper pattern and
placing it in the appropriate session or gamification service layer. Move the
session, profile, badgeLevelUp, and newBadge mapping there, then remove the DTO
factory and update callers to use the mapper while preserving the current
defaults and response values.
src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt (1)

22-27: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Align the @Schema time pattern with the actual schedule rule. The current regex documents 00:0023:00, but the schedule validator only accepts 15:0020:00 on the hour.

🤖 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/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt`
around lines 22 - 27, The `@Schema` pattern on UserSettingsRequest.time currently
permits every hour, while schedule validation only accepts 15:00 through 20:00.
Update the pattern to document exactly those allowed hourly values, preserving
the nullable field and existing example.
src/main/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt (1)

22-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate duplicated profile lookup.

getUserProfileResponse directly calls userProfileRepository.findById(userId).orElseThrow { ... } (lines 23-26), duplicating the exact logic in getUserProfile (lines 43-45). Use the helper to keep error handling consistent and reduce duplication.

♻️ Proposed refactor
 fun getUserProfileResponse(userId: Long): UserProfileResponse {
     val profile =
-        userProfileRepository.findById(userId).orElseThrow {
-            CustomException(DomainErrorCode.USER_NOT_FOUND)
-        }
+        getUserProfile(userId)
     val userCurriculum = getUserCurriculum(userId)

Also applies to: 43-45

🤖 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/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt`
around lines 22 - 26, Update getUserProfileResponse to obtain the profile
through the existing getUserProfile helper instead of directly calling
userProfileRepository.findById(...).orElseThrow(...). Preserve the current
USER_NOT_FOUND behavior and leave the helper as the single profile-lookup
implementation.
🤖 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/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonContent.kt`:
- Around line 1-3: Rename the outbound DTO class LessonContent to
LessonContentResponse, preserving its constructor properties and data-class
behavior, and update all references including
CurriculumService.getLessonContent() and its callers to use the new symbol.

In
`@src/main/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumService.kt`:
- Around line 64-79: Update the null-handling in
CurriculumService.getLessonContent to throw a dedicated question-not-found error
instead of DomainErrorCode.LESSON_TOPIC_NOT_FOUND. Add a unique
LESSON_QUESTION_NOT_FOUND entry to DomainErrorCode, using the project’s existing
error-code conventions, and reference it when
lessonQuestionRepository.findByLessonTopicIdAndPhase returns null; preserve the
existing hint-note error handling.

In
`@src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`:
- Around line 34-36: Update the completed-session branch in
SessionCompletionService to replace the no-argument orElseThrow on
userProfileRepository.findById(userId) with the same
CustomException(DomainErrorCode.USER_NOT_FOUND) supplier used in the normal
path, preserving the RewardResponse behavior when the profile exists.

In
`@src/main/kotlin/org/prography/samsung/backend/session/service/SessionLifecycleService.kt`:
- Around line 33-84: Enforce at the persistence layer that each user can have at
most one TutoringSession with SessionStatus.STARTED, using a database unique
constraint or equivalent atomic locking/conditional insert. Update the
repository/entity configuration around start and findByUserIdAndStatus so
concurrent SessionLifecycleUsecase.start requests cannot create duplicate active
sessions, while preserving the existing resume behavior.

In
`@src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionLifeCycleUsecase.kt`:
- Around line 14-15: Rename SessionLifeCycleUsecase to SessionLifecycleUsecase,
including the Kotlin file, class declaration, and all references. Update the
import and field usage in SessionController to use the consistently spelled
symbol.
- Around line 20-27: Remove the local targetCurriculumId calculation and
CURRICULUM_MISMATCH check from SessionLifeCycleUsecase.start, leaving curriculum
validation and existing-session resume behavior to
SessionLifecycleService.start. Preserve the existing user and curriculum lookups
and delegate directly with the original request.

In
`@src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserSettingsResponse.kt`:
- Line 4: Remove the redundant UserScheduleResponse import from
UserSettingsResponse, since both classes share the same package; leave all
usages and other imports unchanged.

In
`@src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt`:
- Line 27: Rename the test class `SessionServiceTest` to
`SessionLifecycleServiceTest` so it matches the `SessionLifecycleService` SUT
and its existing display name.

---

Outside diff comments:
In
`@src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`:
- Around line 21-27: Remove direct UserProfileRepository,
UserCurriculumRepository, and BadgeLevelRepository dependencies from
SessionCompletionService, and delegate profile/curriculum progress updates and
badge selection to the owning domain service(s). Keep CoinLedgerEntryRepository
in the session service and preserve the existing completion flow through
service-level operations.
- Around line 79-88: The session reward logic in complete() is vulnerable to
concurrent duplicate rewards because existsBySessionId() and save() are not
atomic and the flow lacks a transaction boundary. Add a persistence-level
uniqueness/atomic guard for the session reward, make complete() transactional so
profile updates and CoinLedgerEntry persistence commit as one unit, and handle
duplicate attempts without awarding coins twice.

In
`@src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt`:
- Line 126: Extend SessionQueryServiceTest with the pending-reward (“미수령 보상 세션”)
getStatus scenario removed from SessionServiceTest. Recreate the equivalent
setup and assertion for the pending-reward session, ensuring the test verifies
the expected getStatus result.

---

Nitpick comments:
In @.agents/skills/architecture-skill/report-template.md:
- Line 5: Update the fenced code block in the report template’s Markdown content
to include the `markdown` language specifier, preserving the existing template
content unchanged.

In @.agents/skills/architecture-skill/SKILL.md:
- Around line 33-41: Update the fenced code block containing the architecture
path mappings in SKILL.md to specify the text language, changing the opening
fence to use text while preserving its contents.

In
`@src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt`:
- Around line 18-31: Extract the mapping logic from RewardResponse.from into a
dedicated RewardMapper, following the existing HintNoteMapper pattern and
placing it in the appropriate session or gamification service layer. Move the
session, profile, badgeLevelUp, and newBadge mapping there, then remove the DTO
factory and update callers to use the mapper while preserving the current
defaults and response values.

In
`@src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt`:
- Around line 22-27: The `@Schema` pattern on UserSettingsRequest.time currently
permits every hour, while schedule validation only accepts 15:00 through 20:00.
Update the pattern to document exactly those allowed hourly values, preserving
the nullable field and existing example.

In
`@src/main/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt`:
- Around line 22-26: Update getUserProfileResponse to obtain the profile through
the existing getUserProfile helper instead of directly calling
userProfileRepository.findById(...).orElseThrow(...). Preserve the current
USER_NOT_FOUND behavior and leave the helper as the single profile-lookup
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40b705b0-29be-4294-b2ab-a44cce61c36d

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1a4d4 and 0a5dabe.

📒 Files selected for processing (76)
  • .agents/skills/architecture-skill/SKILL.md
  • .agents/skills/architecture-skill/report-template.md
  • .agents/skills/architecture-skill/rules.md
  • .claude/skills/architecture-skill/SKILL.md
  • .claude/skills/architecture-skill/report-template.md
  • .claude/skills/architecture-skill/rules.md
  • .gitignore
  • AGENTS.md
  • src/main/kotlin/org/prography/samsung/backend/common/dto/SharedDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/common/response/DomainErrorCode.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/CurriculumChipResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/CurriculumSummaryResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/HintNoteResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonContent.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonQuestionResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/TodayTopicResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumService.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/service/HintNoteMapper.kt
  • src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/LevelResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/controller/NotificationController.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/dto/request/NotificationRegisterRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/dto/response/NotificationRegisterResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/dto/response/NotificationRescheduleResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/service/NotificationService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/controller/SessionController.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/SessionDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/request/SessionCompleteRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/request/SessionStartRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/ActiveSessionResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/RewardAckResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionAbortResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionHistoryItemResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionHistoryResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionLessonResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionPhaseResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionStartResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionStatusResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionTodayResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionHistoryService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionLifecycleService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionQueryService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionRewardService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionCompletionUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionHistoryUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionLessonUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionLifeCycleUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/user/controller/OnboardingController.kt
  • src/main/kotlin/org/prography/samsung/backend/user/controller/UserController.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/UserDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/request/OnboardingRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserScheduleRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/OnboardingCompleteResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/OnboardingResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/OnboardingStatusResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserHomeResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserProfileResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserScheduleResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserSettingsResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/service/OnboardingService.kt
  • src/main/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt
  • src/main/kotlin/org/prography/samsung/backend/user/service/UserSettingsService.kt
  • src/main/kotlin/org/prography/samsung/backend/user/usecase/OnboardingUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/user/usecase/UserHomeUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/user/usecase/UserProfileUsecase.kt
  • src/test/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/notification/service/NotificationServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/UserApiIntegrationTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/service/OnboardingServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/service/UserProfileServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/service/UserSettingsServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/usecase/UserHomeUsecaseTest.kt
💤 Files with no reviewable changes (4)
  • src/main/kotlin/org/prography/samsung/backend/user/dto/UserDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/SessionDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/common/dto/SharedDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionService.kt

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt (2)

21-27: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Move cross-domain reward updates behind domain services

UserProfileRepository, UserCurriculumRepository, and BadgeLevelRepository live outside the session domain, so SessionCompletionService is now handling user/gamification mutations directly. Keep CoinLedgerEntryRepository here, but move the profile/curriculum progress update and badge selection behind the owning service(s) instead of injecting those repositories into session.

🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`
around lines 21 - 27, Remove direct UserProfileRepository,
UserCurriculumRepository, and BadgeLevelRepository dependencies from
SessionCompletionService, and delegate profile/curriculum progress updates and
badge selection to the owning domain service(s). Keep CoinLedgerEntryRepository
in the session service and preserve the existing completion flow through
service-level operations.

79-88: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the session reward path atomic
existsBySessionId() is still a check-then-act gap here, and the completion flow has no transaction boundary. Two concurrent complete() calls can both apply the profile reward before any later session conflict, so one session can still mint coins twice. Add an atomic guard at the persistence layer and commit the whole flow as one unit.

🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`
around lines 79 - 88, The session reward logic in complete() is vulnerable to
concurrent duplicate rewards because existsBySessionId() and save() are not
atomic and the flow lacks a transaction boundary. Add a persistence-level
uniqueness/atomic guard for the session reward, make complete() transactional so
profile updates and CoinLedgerEntry persistence commit as one unit, and handle
duplicate attempts without awarding coins twice.
src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt (1)

126-126: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add the pending-reward getStatus case to SessionQueryServiceTest.
The 미수령 보상 세션 coverage was removed from SessionServiceTest, and there’s no matching test in SessionQueryServiceTest. Add the equivalent assertion there to keep getStatus covered.

🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt`
at line 126, Extend SessionQueryServiceTest with the pending-reward (“미수령 보상
세션”) getStatus scenario removed from SessionServiceTest. Recreate the equivalent
setup and assertion for the pending-reward session, ensuring the test verifies
the expected getStatus result.
🧹 Nitpick comments (5)
.agents/skills/architecture-skill/SKILL.md (1)

33-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider specifying a language for the fenced code block.

The code block starting at line 33 has no language specifier, triggering markdownlint MD040. Adding text as the language keeps it informative for syntax highlighters.

♻️ Optional fix

`
- +text
.../{domain}/controller/X.kt → (domain, controller)

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/skills/architecture-skill/SKILL.md around lines 33 - 41, Update the
fenced code block containing the architecture path mappings in SKILL.md to
specify the text language, changing the opening fence to use text while
preserving its contents.


</details>

<!-- cr-comment:v1:d72c9eca2f05b6e52c5900c4 -->

_Source: Linters/SAST tools_

</blockquote></details>
<details>
<summary>.agents/skills/architecture-skill/report-template.md (1)</summary><blockquote>

`5-5`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_

**Consider specifying a language for the fenced code block.**

The code block starting at line 5 has no language specifier (markdownlint MD040). Since it contains Markdown template content, `markdown` would be the appropriate language.





<details>
<summary>♻️ Optional fix</summary>

```diff
-```
+```markdown
 ## 🏛 아키텍처 검증 리포트
🤖 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 @.agents/skills/architecture-skill/report-template.md at line 5, Update the
fenced code block in the report template’s Markdown content to include the
`markdown` language specifier, preserving the existing template content
unchanged.

Source: Linters/SAST tools

src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt (1)

18-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the from() mapping logic to a dedicated mapper.

RewardResponse.from() imports entities from three domains (session, user, gamification), coupling a gamification DTO to session and user entities. The project already uses a separate HintNoteMapper pattern in the curriculum domain. Moving this mapping to a dedicated mapper (e.g., RewardMapper in the session or gamification service layer) would keep the DTO pure and centralize cross-domain coordination where it's architecturally appropriate.

This is not blocking — the factory pattern is idiomatic Kotlin and the logic itself is correct.

🤖 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/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt`
around lines 18 - 31, Extract the mapping logic from RewardResponse.from into a
dedicated RewardMapper, following the existing HintNoteMapper pattern and
placing it in the appropriate session or gamification service layer. Move the
session, profile, badgeLevelUp, and newBadge mapping there, then remove the DTO
factory and update callers to use the mapper while preserving the current
defaults and response values.
src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt (1)

22-27: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Align the @Schema time pattern with the actual schedule rule. The current regex documents 00:0023:00, but the schedule validator only accepts 15:0020:00 on the hour.

🤖 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/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt`
around lines 22 - 27, The `@Schema` pattern on UserSettingsRequest.time currently
permits every hour, while schedule validation only accepts 15:00 through 20:00.
Update the pattern to document exactly those allowed hourly values, preserving
the nullable field and existing example.
src/main/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt (1)

22-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate duplicated profile lookup.

getUserProfileResponse directly calls userProfileRepository.findById(userId).orElseThrow { ... } (lines 23-26), duplicating the exact logic in getUserProfile (lines 43-45). Use the helper to keep error handling consistent and reduce duplication.

♻️ Proposed refactor
 fun getUserProfileResponse(userId: Long): UserProfileResponse {
     val profile =
-        userProfileRepository.findById(userId).orElseThrow {
-            CustomException(DomainErrorCode.USER_NOT_FOUND)
-        }
+        getUserProfile(userId)
     val userCurriculum = getUserCurriculum(userId)

Also applies to: 43-45

🤖 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/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt`
around lines 22 - 26, Update getUserProfileResponse to obtain the profile
through the existing getUserProfile helper instead of directly calling
userProfileRepository.findById(...).orElseThrow(...). Preserve the current
USER_NOT_FOUND behavior and leave the helper as the single profile-lookup
implementation.
🤖 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/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonContent.kt`:
- Around line 1-3: Rename the outbound DTO class LessonContent to
LessonContentResponse, preserving its constructor properties and data-class
behavior, and update all references including
CurriculumService.getLessonContent() and its callers to use the new symbol.

In
`@src/main/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumService.kt`:
- Around line 64-79: Update the null-handling in
CurriculumService.getLessonContent to throw a dedicated question-not-found error
instead of DomainErrorCode.LESSON_TOPIC_NOT_FOUND. Add a unique
LESSON_QUESTION_NOT_FOUND entry to DomainErrorCode, using the project’s existing
error-code conventions, and reference it when
lessonQuestionRepository.findByLessonTopicIdAndPhase returns null; preserve the
existing hint-note error handling.

In
`@src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`:
- Around line 34-36: Update the completed-session branch in
SessionCompletionService to replace the no-argument orElseThrow on
userProfileRepository.findById(userId) with the same
CustomException(DomainErrorCode.USER_NOT_FOUND) supplier used in the normal
path, preserving the RewardResponse behavior when the profile exists.

In
`@src/main/kotlin/org/prography/samsung/backend/session/service/SessionLifecycleService.kt`:
- Around line 33-84: Enforce at the persistence layer that each user can have at
most one TutoringSession with SessionStatus.STARTED, using a database unique
constraint or equivalent atomic locking/conditional insert. Update the
repository/entity configuration around start and findByUserIdAndStatus so
concurrent SessionLifecycleUsecase.start requests cannot create duplicate active
sessions, while preserving the existing resume behavior.

In
`@src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionLifeCycleUsecase.kt`:
- Around line 14-15: Rename SessionLifeCycleUsecase to SessionLifecycleUsecase,
including the Kotlin file, class declaration, and all references. Update the
import and field usage in SessionController to use the consistently spelled
symbol.
- Around line 20-27: Remove the local targetCurriculumId calculation and
CURRICULUM_MISMATCH check from SessionLifeCycleUsecase.start, leaving curriculum
validation and existing-session resume behavior to
SessionLifecycleService.start. Preserve the existing user and curriculum lookups
and delegate directly with the original request.

In
`@src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserSettingsResponse.kt`:
- Line 4: Remove the redundant UserScheduleResponse import from
UserSettingsResponse, since both classes share the same package; leave all
usages and other imports unchanged.

In
`@src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt`:
- Line 27: Rename the test class `SessionServiceTest` to
`SessionLifecycleServiceTest` so it matches the `SessionLifecycleService` SUT
and its existing display name.

---

Outside diff comments:
In
`@src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`:
- Around line 21-27: Remove direct UserProfileRepository,
UserCurriculumRepository, and BadgeLevelRepository dependencies from
SessionCompletionService, and delegate profile/curriculum progress updates and
badge selection to the owning domain service(s). Keep CoinLedgerEntryRepository
in the session service and preserve the existing completion flow through
service-level operations.
- Around line 79-88: The session reward logic in complete() is vulnerable to
concurrent duplicate rewards because existsBySessionId() and save() are not
atomic and the flow lacks a transaction boundary. Add a persistence-level
uniqueness/atomic guard for the session reward, make complete() transactional so
profile updates and CoinLedgerEntry persistence commit as one unit, and handle
duplicate attempts without awarding coins twice.

In
`@src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt`:
- Line 126: Extend SessionQueryServiceTest with the pending-reward (“미수령 보상 세션”)
getStatus scenario removed from SessionServiceTest. Recreate the equivalent
setup and assertion for the pending-reward session, ensuring the test verifies
the expected getStatus result.

---

Nitpick comments:
In @.agents/skills/architecture-skill/report-template.md:
- Line 5: Update the fenced code block in the report template’s Markdown content
to include the `markdown` language specifier, preserving the existing template
content unchanged.

In @.agents/skills/architecture-skill/SKILL.md:
- Around line 33-41: Update the fenced code block containing the architecture
path mappings in SKILL.md to specify the text language, changing the opening
fence to use text while preserving its contents.

In
`@src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt`:
- Around line 18-31: Extract the mapping logic from RewardResponse.from into a
dedicated RewardMapper, following the existing HintNoteMapper pattern and
placing it in the appropriate session or gamification service layer. Move the
session, profile, badgeLevelUp, and newBadge mapping there, then remove the DTO
factory and update callers to use the mapper while preserving the current
defaults and response values.

In
`@src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt`:
- Around line 22-27: The `@Schema` pattern on UserSettingsRequest.time currently
permits every hour, while schedule validation only accepts 15:00 through 20:00.
Update the pattern to document exactly those allowed hourly values, preserving
the nullable field and existing example.

In
`@src/main/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt`:
- Around line 22-26: Update getUserProfileResponse to obtain the profile through
the existing getUserProfile helper instead of directly calling
userProfileRepository.findById(...).orElseThrow(...). Preserve the current
USER_NOT_FOUND behavior and leave the helper as the single profile-lookup
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40b705b0-29be-4294-b2ab-a44cce61c36d

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1a4d4 and 0a5dabe.

📒 Files selected for processing (76)
  • .agents/skills/architecture-skill/SKILL.md
  • .agents/skills/architecture-skill/report-template.md
  • .agents/skills/architecture-skill/rules.md
  • .claude/skills/architecture-skill/SKILL.md
  • .claude/skills/architecture-skill/report-template.md
  • .claude/skills/architecture-skill/rules.md
  • .gitignore
  • AGENTS.md
  • src/main/kotlin/org/prography/samsung/backend/common/dto/SharedDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/common/response/DomainErrorCode.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/CurriculumChipResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/CurriculumSummaryResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/HintNoteResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonContent.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonQuestionResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/TodayTopicResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumService.kt
  • src/main/kotlin/org/prography/samsung/backend/curriculum/service/HintNoteMapper.kt
  • src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/LevelResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/gamification/dto/response/RewardResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/controller/NotificationController.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/dto/request/NotificationRegisterRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/dto/response/NotificationRegisterResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/dto/response/NotificationRescheduleResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/notification/service/NotificationService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/controller/SessionController.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/SessionDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/request/SessionCompleteRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/request/SessionStartRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/ActiveSessionResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/RewardAckResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionAbortResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionHistoryItemResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionHistoryResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionLessonResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionPhaseResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionStartResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionStatusResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/response/SessionTodayResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionHistoryService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionLifecycleService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionQueryService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionRewardService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionService.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionCompletionUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionHistoryUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionLessonUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionLifeCycleUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/user/controller/OnboardingController.kt
  • src/main/kotlin/org/prography/samsung/backend/user/controller/UserController.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/UserDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/request/OnboardingRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserScheduleRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/request/UserSettingsRequest.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/OnboardingCompleteResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/OnboardingResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/OnboardingStatusResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserHomeResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserProfileResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserScheduleResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserSettingsResponse.kt
  • src/main/kotlin/org/prography/samsung/backend/user/service/OnboardingService.kt
  • src/main/kotlin/org/prography/samsung/backend/user/service/UserProfileService.kt
  • src/main/kotlin/org/prography/samsung/backend/user/service/UserSettingsService.kt
  • src/main/kotlin/org/prography/samsung/backend/user/usecase/OnboardingUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/user/usecase/UserHomeUsecase.kt
  • src/main/kotlin/org/prography/samsung/backend/user/usecase/UserProfileUsecase.kt
  • src/test/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/notification/service/NotificationServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/UserApiIntegrationTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/service/OnboardingServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/service/UserProfileServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/service/UserSettingsServiceTest.kt
  • src/test/kotlin/org/prography/samsung/backend/user/usecase/UserHomeUsecaseTest.kt
💤 Files with no reviewable changes (4)
  • src/main/kotlin/org/prography/samsung/backend/user/dto/UserDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/session/dto/SessionDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/common/dto/SharedDtos.kt
  • src/main/kotlin/org/prography/samsung/backend/session/service/SessionService.kt
🛑 Comments failed to post (8)
src/main/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonContent.kt (1)

1-3: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Rename LessonContent to LessonContentResponse to follow the DTO suffix convention.

As per coding guidelines, outbound DTOs must use the *Response suffix. LessonContent is in the dto/response package and returned by CurriculumService.getLessonContent(), making it an outbound DTO. All sibling DTOs in the same package already follow this convention.

♻️ Proposed rename
-data class LessonContent(val question: LessonQuestionResponse, val hintNote: HintNoteResponse)
+data class LessonContentResponse(val question: LessonQuestionResponse, val hintNote: HintNoteResponse)
📝 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.

package org.prography.samsung.backend.curriculum.dto.response

data class LessonContentResponse(val question: LessonQuestionResponse, val hintNote: HintNoteResponse)
🤖 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/kotlin/org/prography/samsung/backend/curriculum/dto/response/LessonContent.kt`
around lines 1 - 3, Rename the outbound DTO class LessonContent to
LessonContentResponse, preserving its constructor properties and data-class
behavior, and update all references including
CurriculumService.getLessonContent() and its callers to use the new symbol.

Source: Coding guidelines

src/main/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumService.kt (1)

64-79: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wrong error code thrown when lesson question is not found.

Line 67 throws DomainErrorCode.LESSON_TOPIC_NOT_FOUND ("학습 주제를 찾을 수 없습니다") when lessonQuestionRepository.findByLessonTopicIdAndPhase returns null. The question is missing, not the topic. This produces a misleading 404 error message for API consumers who know the topic exists.

Consider adding a LESSON_QUESTION_NOT_FOUND entry to DomainErrorCode (e.g., code 40460) and using it here instead. As per coding guidelines, new domain error codes should be added to DomainErrorCode with a unique code.

🐛 Proposed fix

In DomainErrorCode.kt:

     HINT_NOTE_NOT_FOUND(HttpStatus.NOT_FOUND, 40450, "힌트 노트를 찾을 수 없습니다."),
+    LESSON_QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, 40460, "학습 문제를 찾을 수 없습니다."),

In CurriculumService.kt:

-        val question = lessonQuestionRepository.findByLessonTopicIdAndPhase(lessonTopicId, phase)
-            ?: throw CustomException(DomainErrorCode.LESSON_TOPIC_NOT_FOUND)
+        val question = lessonQuestionRepository.findByLessonTopicIdAndPhase(lessonTopicId, phase)
+            ?: throw CustomException(DomainErrorCode.LESSON_QUESTION_NOT_FOUND)
🤖 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/kotlin/org/prography/samsung/backend/curriculum/service/CurriculumService.kt`
around lines 64 - 79, Update the null-handling in
CurriculumService.getLessonContent to throw a dedicated question-not-found error
instead of DomainErrorCode.LESSON_TOPIC_NOT_FOUND. Add a unique
LESSON_QUESTION_NOT_FOUND entry to DomainErrorCode, using the project’s existing
error-code conventions, and reference it when
lessonQuestionRepository.findByLessonTopicIdAndPhase returns null; preserve the
existing hint-note error handling.

Source: Coding guidelines

src/main/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt (1)

34-36: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Raw NoSuchElementException instead of CustomException.

userProfileRepository.findById(userId).orElseThrow() (no-arg) throws NoSuchElementException when empty, bypassing the domain error contract used everywhere else in this same method (Lines 43-45 use .orElseThrow { CustomException(DomainErrorCode.USER_NOT_FOUND) }). If the profile is missing on this already-completed-session path, the caller gets an unhandled raw runtime exception instead of a proper CustomException(DomainErrorCode...).

🐛 Proposed fix
         if (session.status == SessionStatus.COMPLETED) {
-            return RewardResponse.from(session, userProfileRepository.findById(userId).orElseThrow())
+            val profile =
+                userProfileRepository.findById(userId).orElseThrow {
+                    CustomException(DomainErrorCode.USER_NOT_FOUND)
+                }
+            return RewardResponse.from(session, profile)
         }

As per path instructions, src/main/kotlin/org/prography/samsung/backend/**/service/**/*.kt: "In services, throw CustomException with DomainErrorCode; never throw raw RuntimeException or ErrorBaseCode directly."

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

        if (session.status == SessionStatus.COMPLETED) {
            val profile =
                userProfileRepository.findById(userId).orElseThrow {
                    CustomException(DomainErrorCode.USER_NOT_FOUND)
                }
            return RewardResponse.from(session, profile)
        }
🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionCompletionService.kt`
around lines 34 - 36, Update the completed-session branch in
SessionCompletionService to replace the no-argument orElseThrow on
userProfileRepository.findById(userId) with the same
CustomException(DomainErrorCode.USER_NOT_FOUND) supplier used in the normal
path, preserving the RewardResponse behavior when the profile exists.

Source: Path instructions

src/main/kotlin/org/prography/samsung/backend/session/service/SessionLifecycleService.kt (1)

33-84: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -a 'SessionLifeCycleUsecase.kt' --exec cat {}
rg -n 'Version|uniqueConstraints|Index|Table' src/main/kotlin/org/prography/samsung/backend/session/entity/TutoringSession.kt

Repository: prography/11th-3team-BE

Length of output: 2074


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the tutoring session entity and nearby annotations.
entity=$(fd -a 'TutoringSession.kt' src/main/kotlin/org/prography/samsung/backend/session/entity | head -n 1)
echo "ENTITY=$entity"
wc -l "$entity"
sed -n '1,220p' "$entity"

# Search for constraints or schema definitions related to tutoring_sessions / active sessions.
rg -n 'tutoring_sessions|uniqueConstraints|`@Index`|`@Table`|active session|STARTED|SessionStatus.STARTED|user_id' \
  src/main/kotlin src/main/resources db . 2>/dev/null | sed -n '1,220p'

Repository: prography/11th-3team-BE

Length of output: 29400


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the session creation table definition for any uniqueness guard on active sessions.
sed -n '1,80p' src/main/resources/db/migration/V4__create_session_tables.sql

# Check whether any migration adds a uniqueness constraint tied to tutoring_sessions.user_id/status.
rg -n 'UNIQUE|unique|idx_tutoring_sessions_user_status|tutoring_sessions \(user_id|user_id.*status|status.*user_id' \
  src/main/resources/db/migration src/main/kotlin/org/prography/samsung/backend/session \
  | sed -n '1,160p'

Repository: prography/11th-3team-BE

Length of output: 3233


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the active-session lookup uses any pessimistic/optimistic locking.
rg -n '`@Lock`|PESSIMISTIC|OPTIMISTIC|findByUserIdAndStatus|tutoringSessionRepository' \
  src/main/kotlin/org/prography/samsung/backend/session src/main/kotlin/org/prography/samsung/backend/user \
  | sed -n '1,200p'

# Read the session repository interface.
repo=$(fd -a 'TutoringSessionRepository.kt' src/main/kotlin | head -n 1)
echo "REPO=$repo"
wc -l "$repo"
sed -n '1,220p' "$repo"

Repository: prography/11th-3team-BE

Length of output: 7154


Prevent duplicate STARTED sessions at the database layer. SessionLifeCycleUsecase.start() is transactional, but the findByUserIdAndStatus(...) check is still race-prone: two concurrent requests can both miss the existing row and insert separate STARTED sessions. Add a unique constraint or equivalent lock/conditional insert to enforce one active session per user.

🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionLifecycleService.kt`
around lines 33 - 84, Enforce at the persistence layer that each user can have
at most one TutoringSession with SessionStatus.STARTED, using a database unique
constraint or equivalent atomic locking/conditional insert. Update the
repository/entity configuration around start and findByUserIdAndStatus so
concurrent SessionLifecycleUsecase.start requests cannot create duplicate active
sessions, while preserving the existing resume behavior.
src/main/kotlin/org/prography/samsung/backend/session/usecase/SessionLifeCycleUsecase.kt (2)

14-15: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align class naming: LifeCycleLifecycle for consistency with SessionLifecycleService.

The service uses SessionLifecycleService (lowercase 'c') but the usecase uses SessionLifeCycleUsecase (uppercase 'C'). "Lifecycle" is the standard spelling and matches the service naming.

♻️ Proposed fix
-class SessionLifeCycleUsecase(
+class SessionLifecycleUsecase(
     private val sessionLifecycleService: SessionLifecycleService,
     private val userProfileService: UserProfileService,
 )

Also rename the file to SessionLifecycleUsecase.kt and update the import/field in SessionController.kt.

🤖 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/kotlin/org/prography/samsung/backend/session/usecase/SessionLifeCycleUsecase.kt`
around lines 14 - 15, Rename SessionLifeCycleUsecase to SessionLifecycleUsecase,
including the Kotlin file, class declaration, and all references. Update the
import and field usage in SessionController to use the consistently spelled
symbol.

20-27: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove redundant curriculum mismatch check — it's incomplete and duplicates the service's validation.

The usecase checks only request?.curriculumId against userCurriculum.curriculum.id, but SessionLifecycleService.start already performs a more thorough check that also covers selectedTopic?.curriculum?.id (i.e., lessonTopicId-based mismatch). The usecase's check is a strict subset and violates DRY.

Additionally, this introduces a behavioral change: the service returns an existing STARTED session early (resumed=true) before its mismatch check, but the usecase's check runs before delegation. So a resume request with a mismatched curriculumId would now throw CURRICULUM_MISMATCH instead of returning the existing session.

♻️ Proposed fix: remove the redundant check and let the service validate
 `@Transactional`
 fun start(userId: Long, request: SessionStartRequest?): SessionStartResponse {
     val user = userProfileService.getUser(userId)
     val userCurriculum = userProfileService.getUserCurriculum(userId)
-    val targetCurriculumId = request?.curriculumId ?: userCurriculum.curriculum.id
-    if (userCurriculum.curriculum.id != targetCurriculumId) {
-        throw CustomException(DomainErrorCode.CURRICULUM_MISMATCH)
-    }
     return sessionLifecycleService.start(user, userCurriculum, request)
 }
📝 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.

    fun start(userId: Long, request: SessionStartRequest?): SessionStartResponse {
        val user = userProfileService.getUser(userId)
        val userCurriculum = userProfileService.getUserCurriculum(userId)
        return sessionLifecycleService.start(user, userCurriculum, request)
🤖 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/kotlin/org/prography/samsung/backend/session/usecase/SessionLifeCycleUsecase.kt`
around lines 20 - 27, Remove the local targetCurriculumId calculation and
CURRICULUM_MISMATCH check from SessionLifeCycleUsecase.start, leaving curriculum
validation and existing-session resume behavior to
SessionLifecycleService.start. Preserve the existing user and curriculum lookups
and delegate directly with the original request.
src/main/kotlin/org/prography/samsung/backend/user/dto/response/UserSettingsResponse.kt (1)

4-4: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove redundant same-package import.

UserScheduleResponse is in the same package (org.prography.samsung.backend.user.dto.response) as this file, so the import is unnecessary. ktlint's no-unused-imports rule will flag this, causing ./gradlew ktlintCheck to fail. As per coding guidelines, ktlint must be clean before committing.

♻️ Proposed fix
 import org.prography.samsung.backend.curriculum.dto.response.CurriculumChipResponse
-import org.prography.samsung.backend.user.dto.response.UserScheduleResponse
 
 data class UserSettingsResponse(val curriculum: CurriculumChipResponse, val schedule: UserScheduleResponse)
📝 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.


🤖 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/kotlin/org/prography/samsung/backend/user/dto/response/UserSettingsResponse.kt`
at line 4, Remove the redundant UserScheduleResponse import from
UserSettingsResponse, since both classes share the same package; leave all
usages and other imports unchanged.

Source: Coding guidelines

src/test/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt (1)

27-27: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename test class to match the SUT.

The class is SessionServiceTest but the SUT is SessionLifecycleService and the display name says "SessionLifecycleService 단위 테스트". Rename to SessionLifecycleServiceTest for clarity.

♻️ Proposed fix
-@DisplayName("SessionLifecycleService 단위 테스트")
-class SessionServiceTest {
+@DisplayName("SessionLifecycleService 단위 테스트")
+class SessionLifecycleServiceTest {
📝 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.

class SessionLifecycleServiceTest {
🤖 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/kotlin/org/prography/samsung/backend/session/service/SessionServiceTest.kt`
at line 27, Rename the test class `SessionServiceTest` to
`SessionLifecycleServiceTest` so it matches the `SessionLifecycleService` SUT
and its existing display name.

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.

1 participant