Skip to content

[Feat] 약관 관련 API 구현#210

Merged
hyomee2 merged 9 commits intodevelopfrom
feat/#202-terms
Mar 26, 2026
Merged

[Feat] 약관 관련 API 구현#210
hyomee2 merged 9 commits intodevelopfrom
feat/#202-terms

Conversation

@hyomee2
Copy link
Copy Markdown
Collaborator

@hyomee2 hyomee2 commented Mar 26, 2026

Related issue 🛠

Work Description 📝

  • 약관 관련 API를 구현했습니다.
  • 프론트엔드 담당자와 협의하여 API 요청 및 응답 스펙에 맞게 구현했습니다. (노션 API 명세서 확인 가능)
  • 약관 동의의 경우에는 온보딩 전에 이루어지므로 security filter에서 온보딩 전에도 접근할 수 있도록 해주었습니다.

1. 약관 조회 API

  • active 컬럼을 넣어서 active가 true인 것만 조회되게 했고, 프론트와 협의해 정렬하여 반환하도록 했습니다.

2. 약관 동의 저장 API

  1. 받아야 하는 약관인데 받지 않은 경우
  2. 중복된 약관을 받은 경우
  3. 필수 약관인데 동의하지 않은 경우
    에 대해 예외를 던지게 했습니다.

ScreenShots 📷

  1. 약관 조회 API
image
  1. 약관 동의 저장 API
    (1) 누락된 약관 동의가 있는 경우
image

(2) 중복된 약관 동의가 있는경우
image

(3) 필수 약관에 동의하지 않은 경우
image

(4) 이미 약관 동의 내용이 있는 경우
image

(5) 성공
image

To Reviewers 📢

  • 테스트 코드는 별도의 PR로 구현해서 올리겠슴니다

Summary by CodeRabbit

  • 새로운 기능
    • 이용약관 조회 기능 추가
    • 회원 약관 동의 제출 기능 추가
    • 필수 약관 동의 여부 검증 도입
    • 약관 타입별 분류 및 버전 관리 기능 추가

@hyomee2 hyomee2 requested review from eraser502 and jeong1112 and removed request for eraser502 March 26, 2026 16:16
@hyomee2 hyomee2 self-assigned this Mar 26, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

약관 도메인을 추가하고 회원의 약관동의 흐름을 구현합니다. Term 및 MemberTerm 엔티티, 관련 리포지토리·서비스·컨트롤러·DTO를 추가하고, 회원의 약관 동의(POST /api/v1/members/term-agreements) 엔드포인트와 약관 조회(GET /api/v1/terms)를 도입합니다.

Changes

Cohort / File(s) Summary
Term 도메인 - Entity 및 Enum
src/main/java/org/sopt/kareer/domain/term/entity/Term.java, src/main/java/org/sopt/kareer/domain/term/entity/enums/TermType.java
새로운 Term JPA 엔티티(필드: id, title, content, type, version, required, active) 및 TermType enum(6개 상수, order 필드) 추가
Term 도메인 - 예외 및 에러코드
src/main/java/org/sopt/kareer/domain/term/exception/TermErrorCode.java, src/main/java/org/sopt/kareer/domain/term/exception/TermException.java
Term 관련 에러코드와 TermException 클래스 추가(여러 상세 오류 코드 포함)
Term 도메인 - Repository 및 Service
src/main/java/org/sopt/kareer/domain/term/repository/TermRepository.java, src/main/java/org/sopt/kareer/domain/term/service/TermService.java
TermRepository(findByActiveTrue)와 TermService(getTerms, getActiveTerms) 추가(활성 약관 조회 및 정렬 변환)
Term 도메인 - Controller 및 DTO
src/main/java/org/sopt/kareer/domain/term/controller/TermController.java, src/main/java/org/sopt/kareer/domain/term/dto/response/TermsResponse.java
GET /api/v1/terms 엔드포인트와 TermsResponse DTO 추가(약관 목록 반환)
Member 도메인 - Entity 및 Repository
src/main/java/org/sopt/kareer/domain/member/entity/MemberTerm.java, src/main/java/org/sopt/kareer/domain/member/repository/MemberTermRepository.java
MemberTerm 엔티티(회원-약관 매핑, 유니크 제약) 및 MemberTermRepository(existsByMemberId) 추가
Member 도메인 - Request DTO
src/main/java/org/sopt/kareer/domain/member/dto/request/MemberTermsRequest.java
MemberTermsRequest record 및 중첩 TermAgreement 추가(검증 어노테이션 포함)
Member 도메인 - Service 변경
src/main/java/org/sopt/kareer/domain/member/service/MemberService.java
agreeTerms 메서드 추가: 중복 동의 검사, 요청 중복 termId 검사, 활성 약관 일치 검사, 필수 약관 동의 검증 후 MemberTerm 생성·저장
Member 도메인 - Controller 변경
src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java
POST /api/v1/members/term-agreements 엔드포인트(agreeTerms) 추가(인증된 memberId 및 검증된 요청 바디 처리)
전역 설정 및 필터
src/main/java/org/sopt/kareer/global/config/swagger/SwaggerResponseDescription.java, src/main/java/org/sopt/kareer/global/security/filter/OnboardingRestrictionFilter.java
SwaggerResponseDescription에 TERM_AGREE 항목 추가 및 OnboardingRestrictionFilter의 항상 허용 경로에 /api/v1/terms, /api/v1/members/term-agreements 추가

Sequence Diagram

sequenceDiagram
    participant Client as "Client"
    participant Controller as "MemberController"
    participant Service as "MemberService"
    participant TermSvc as "TermService"
    participant Repo as "MemberTermRepository"
    participant DB as "Database"

    Client->>Controller: POST /api/v1/members/term-agreements\n(memberId, MemberTermsRequest)
    Controller->>Service: agreeTerms(memberId, request)
    Service->>Repo: existsByMemberId(memberId)
    Repo->>DB: SELECT COUNT FROM member_terms WHERE member_id=?
    DB-->>Repo: boolean
    Repo-->>Service: exists

    Service->>TermSvc: getActiveTerms()
    TermSvc->>DB: SELECT * FROM terms WHERE active = true
    DB-->>TermSvc: List<Term>
    TermSvc-->>Service: List<Term>

    Service->>Service: validate(request vs active terms)\n(check duplicates, missing, required)
    Service->>Repo: saveAll(List<MemberTerm>)
    Repo->>DB: INSERT INTO member_terms ...
    DB-->>Repo: result
    Repo-->>Service: savedEntities
    Service-->>Controller: void
    Controller-->>Client: 200 OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • eraser502
  • jeong1112

Poem

🐰 약관 길에 깡총깡총 달려가,
작은 기록 하나하나 모으네,
Term은 빛나고 MemberTerm은 손잡아,
검증 끝나면 모두 함께 웃네,
콩닥콩닥, 동의의 춤을 추자! ✨

🚥 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
Title check ✅ Passed PR 제목이 변경 사항의 핵심을 명확하게 반영합니다. 약관 관련 API 구현이라는 주요 변경 내용이 간결하고 구체적으로 표현되었습니다.
Linked Issues check ✅ Passed 연결된 이슈 #202의 목표인 약관동의 API 구현이 완전히 충족되었습니다. 약관 조회 API, 약관 동의 저장 API, 필수 검증 로직(중복 확인, 필수 약관 검증 등)이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 약관 관련 API 구현 범위 내에 있습니다. 용어, 엔티티, 저장소, 서비스, 컨트롤러, DTO, 예외 처리, 보안 필터 조정이 모두 약관 기능 구현을 위한 필수 변경입니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#202-terms

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/main/java/org/sopt/kareer/global/config/swagger/SwaggerResponseDescription.java (1)

24-24: 와일드카드 import가 다른 import들과 일관성이 없습니다.

파일의 다른 static import들은 모두 명시적으로 개별 에러 코드를 import하고 있는 반면, TermErrorCode만 와일드카드(*)를 사용하고 있습니다. 일관성을 위해 명시적 import를 권장합니다.

♻️ 제안하는 수정
-import static org.sopt.kareer.domain.term.exception.TermErrorCode.*;
+import static org.sopt.kareer.domain.term.exception.TermErrorCode.DUPLICATE_TERM;
+import static org.sopt.kareer.domain.term.exception.TermErrorCode.MISSING_TERM;
+import static org.sopt.kareer.domain.term.exception.TermErrorCode.REQUIRED_TERM_NOT_AGREED;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/sopt/kareer/global/config/swagger/SwaggerResponseDescription.java`
at line 24, The static import for TermErrorCode uses a wildcard (import static
org.sopt.kareer.domain.term.exception.TermErrorCode.*;) which is inconsistent
with other explicit static imports; replace the wildcard with explicit imports
of the specific error constants used in SwaggerResponseDescription (e.g., import
static org.sopt.kareer.domain.term.exception.TermErrorCode.SOME_ERROR; import
static org.sopt.kareer.domain.term.exception.TermErrorCode.ANOTHER_ERROR;) so
the file matches the explicit import style used elsewhere and references the
exact TermErrorCode constants used in this class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/sopt/kareer/domain/member/dto/request/MemberTermsRequest.java`:
- Around line 13-14: Add `@NotNull` to the list element type in MemberTermsRequest
so null entries are rejected at validation time: update the agreements field
declaration (List<@Valid TermAgreement> agreements) to include element-level
`@NotNull` (e.g., List<@NotNull `@Valid` TermAgreement> agreements) so requests like
agreements: [null] fail validation before TermAgreement::termId is accessed.

In `@src/main/java/org/sopt/kareer/domain/member/service/MemberService.java`:
- Around line 258-271: The current flow in MemberService (mapping activeTerms ->
MemberTerm.create(...) then memberTermRepository.saveAll) blindly inserts new
MemberTerm rows causing duplicate (member_id, term_id) entries; change it to
first load existing MemberTerm records for the member for all active term IDs
(use memberTermRepository to fetch by member and term IDs), then for each active
term either update the existing MemberTerm's agreed flag and timestamps or
create a new MemberTerm if none exists (use MemberTerm.create only for new
records), collect updated+new entities and call saveAll to perform upserts; also
ensure the MemberTerm entity/database has a unique constraint on (member_id,
term_id) to enforce uniqueness at DB level.

In `@src/main/java/org/sopt/kareer/domain/term/dto/response/TermsResponse.java`:
- Around line 27-28: The `@Schema` example for the boolean field required in
TermsResponse is a string and must be a boolean; update the `@Schema` annotation
on the required field in the TermsResponse DTO so its example is a boolean
literal (true or false) instead of the current string ("1. Purpose ~"), ensuring
the example type matches the field type.

---

Nitpick comments:
In
`@src/main/java/org/sopt/kareer/global/config/swagger/SwaggerResponseDescription.java`:
- Line 24: The static import for TermErrorCode uses a wildcard (import static
org.sopt.kareer.domain.term.exception.TermErrorCode.*;) which is inconsistent
with other explicit static imports; replace the wildcard with explicit imports
of the specific error constants used in SwaggerResponseDescription (e.g., import
static org.sopt.kareer.domain.term.exception.TermErrorCode.SOME_ERROR; import
static org.sopt.kareer.domain.term.exception.TermErrorCode.ANOTHER_ERROR;) so
the file matches the explicit import style used elsewhere and references the
exact TermErrorCode constants used in this class.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 571cc397-61a3-41f9-85fa-eb5af7605822

📥 Commits

Reviewing files that changed from the base of the PR and between 0142c8a and 0d016a9.

📒 Files selected for processing (15)
  • src/main/java/org/sopt/kareer/domain/member/controller/MemberController.java
  • src/main/java/org/sopt/kareer/domain/member/dto/request/MemberTermsRequest.java
  • src/main/java/org/sopt/kareer/domain/member/entity/MemberTerm.java
  • src/main/java/org/sopt/kareer/domain/member/repository/MemberTermRepository.java
  • src/main/java/org/sopt/kareer/domain/member/service/MemberService.java
  • src/main/java/org/sopt/kareer/domain/term/controller/TermController.java
  • src/main/java/org/sopt/kareer/domain/term/dto/response/TermsResponse.java
  • src/main/java/org/sopt/kareer/domain/term/entity/Term.java
  • src/main/java/org/sopt/kareer/domain/term/entity/enums/TermType.java
  • src/main/java/org/sopt/kareer/domain/term/exception/TermErrorCode.java
  • src/main/java/org/sopt/kareer/domain/term/exception/TermException.java
  • src/main/java/org/sopt/kareer/domain/term/repository/TermRepository.java
  • src/main/java/org/sopt/kareer/domain/term/service/TermService.java
  • src/main/java/org/sopt/kareer/global/config/swagger/SwaggerResponseDescription.java
  • src/main/java/org/sopt/kareer/global/security/filter/OnboardingRestrictionFilter.java

Comment thread src/main/java/org/sopt/kareer/domain/member/dto/request/MemberTermsRequest.java Outdated
Comment thread src/main/java/org/sopt/kareer/domain/term/dto/response/TermsResponse.java Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/main/java/org/sopt/kareer/domain/term/exception/TermErrorCode.java (1)

1-1: TermErrorCodeerrorcode 서브패키지로 분리하는 것을 권장합니다.

Line 1 기준 현재 exception 패키지에 ErrorCode enum이 있어 예외 타입과 코드 정의의 책임이 섞여 보입니다. org.sopt.kareer.domain.term.errorcode로 이동하면 도메인 응집성과 탐색성이 더 좋아집니다.

제안 diff
- package org.sopt.kareer.domain.term.exception;
+ package org.sopt.kareer.domain.term.errorcode;
- public class TermException extends CustomException {
-     public TermException(TermErrorCode errorCode) {
+ import org.sopt.kareer.domain.term.errorcode.TermErrorCode;
+ 
+ public class TermException extends CustomException {
+     public TermException(TermErrorCode errorCode) {
          super(errorCode);
      }
  }

Based on learnings: 도메인별 ErrorCode enum은 각 도메인 패키지 내 errorcode 서브패키지에 위치시켜 도메인 응집성을 높이는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/sopt/kareer/domain/term/exception/TermErrorCode.java` at
line 1, Move the TermErrorCode enum out of the exception package into a new
errorcode subpackage to separate error-code definitions from exception types:
create package org.sopt.kareer.domain.term.errorcode, update the TermErrorCode
file's package declaration accordingly, and update all references/imports to
TermErrorCode in classes like any TermException implementations or handlers so
they import org.sopt.kareer.domain.term.errorcode.TermErrorCode; ensure
build/imports compile after the package rename.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/org/sopt/kareer/domain/term/exception/TermErrorCode.java`:
- Line 1: Move the TermErrorCode enum out of the exception package into a new
errorcode subpackage to separate error-code definitions from exception types:
create package org.sopt.kareer.domain.term.errorcode, update the TermErrorCode
file's package declaration accordingly, and update all references/imports to
TermErrorCode in classes like any TermException implementations or handlers so
they import org.sopt.kareer.domain.term.errorcode.TermErrorCode; ensure
build/imports compile after the package rename.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4eb26b87-12dc-4411-8f02-652158209b5b

📥 Commits

Reviewing files that changed from the base of the PR and between 0d016a9 and 0500d2a.

📒 Files selected for processing (6)
  • src/main/java/org/sopt/kareer/domain/member/dto/request/MemberTermsRequest.java
  • src/main/java/org/sopt/kareer/domain/member/entity/MemberTerm.java
  • src/main/java/org/sopt/kareer/domain/member/repository/MemberTermRepository.java
  • src/main/java/org/sopt/kareer/domain/member/service/MemberService.java
  • src/main/java/org/sopt/kareer/domain/term/dto/response/TermsResponse.java
  • src/main/java/org/sopt/kareer/domain/term/exception/TermErrorCode.java
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/org/sopt/kareer/domain/term/dto/response/TermsResponse.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/org/sopt/kareer/domain/member/repository/MemberTermRepository.java
  • src/main/java/org/sopt/kareer/domain/member/dto/request/MemberTermsRequest.java
  • src/main/java/org/sopt/kareer/domain/member/service/MemberService.java
  • src/main/java/org/sopt/kareer/domain/member/entity/MemberTerm.java

@hyomee2 hyomee2 merged commit 5bdab6b into develop Mar 26, 2026
2 checks passed
@hyomee2 hyomee2 deleted the feat/#202-terms branch March 26, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 약관동의 API 구현

1 participant