Skip to content

Comments

[Feat] 회원가입 시 필수 약관 동의 추가#171

Open
chaechaen wants to merge 1 commit intodevelopfrom
feat/165/Terms
Open

[Feat] 회원가입 시 필수 약관 동의 추가#171
chaechaen wants to merge 1 commit intodevelopfrom
feat/165/Terms

Conversation

@chaechaen
Copy link
Member

@chaechaen chaechaen commented Feb 11, 2026

🚀 변경사항

회원가입 시 필수 약관 동의 저장 로직 추가 (1번 필수 약관 동의 안 하면 회원가입 못함)

🔗 관련 이슈

  • Closes #이슈번호

✅ 체크리스트

  • 로컬에서 테스트 완료
  • 코드 리뷰 준비 완료

📝 특이사항

Summary by CodeRabbit

  • New Features

    • Users now must agree to required terms of service during the signup process.
    • Added validation to ensure all mandatory terms are explicitly accepted before registration can complete.
    • User term agreements are now stored and tracked within the system for compliance purposes.
  • Chores

    • Initialized default terms and policies data in the database.

@chaechaen chaechaen self-assigned this Feb 11, 2026
@chaechaen chaechaen linked an issue Feb 11, 2026 that may be closed by this pull request
1 task
@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

The PR adds terms agreement tracking to the signup flow by introducing an agreedTermsIds parameter that propagates from the signup request through authentication events to the member service, where it's validated against required terms and persisted as member-terms associations. A database migration inserts three default terms records.

Changes

Cohort / File(s) Summary
Request and Event Models
src/main/java/checkmo/authentication/web/dto/AuthRequestDTO.java, src/main/java/checkmo/authentication/AuthenticationEvent.java
Added agreedTermsIds field to SignUp DTO with @NotEmpty validation and updated CreateMember record to include the agreed terms IDs parameter.
Error Handling
src/main/java/checkmo/member/internal/exception/MemberErrorStatus.java
Introduced two new error statuses: TERMS_NOT_FOUND and REQUIRED_TERMS_NOT_AGREE for terms validation scenarios.
Event and Command Flow
src/main/java/checkmo/authentication/internal/service/command/AuthUserCommandService.java, src/main/java/checkmo/member/internal/listener/MemberEventListener.java
Updated signup command to propagate agreedTermsIds into the CreateMember event, and updated event listener to pass agreedTermsIds to the member creation service.
Data Persistence
src/main/java/checkmo/member/internal/service/command/MemberCommandService.java, src/main/java/checkmo/member/internal/repository/MemberTermsRepository.java, src/main/java/checkmo/member/internal/repository/TermsRepository.java
Added two new repository interfaces and enhanced MemberCommandService to validate required terms agreement, fetch Terms entities, and persist MemberTerms associations.
Database Migration
src/main/resources/db/migration/V20260211__insert_default_terms.sql
Added migration script inserting three default terms records: one required personal data collection consent and two optional consents for marketing and third-party data sharing.

Sequence Diagram

sequenceDiagram
    participant Client
    participant AuthUserCommandService
    participant AuthenticationEvent
    participant MemberEventListener
    participant MemberCommandService
    participant TermsRepository
    participant MemberTermsRepository

    Client->>AuthUserCommandService: signUp(email, password, agreedTermsIds)
    AuthUserCommandService->>AuthUserCommandService: email verification & user save
    AuthUserCommandService->>AuthenticationEvent: CreateMember(id, email, agreedTermsIds)
    AuthUserCommandService->>MemberEventListener: publish event
    
    MemberEventListener->>MemberCommandService: createMember(memberId, email, agreedTermsIds)
    MemberCommandService->>MemberCommandService: validateRequiredTerms(agreedTermsIds)
    MemberCommandService->>TermsRepository: fetch all required terms
    
    alt Required terms validation fails
        MemberCommandService->>MemberCommandService: throw REQUIRED_TERMS_NOT_AGREE
    else Validation succeeds
        MemberCommandService->>TermsRepository: fetch each agreed Term by id
        MemberCommandService->>MemberTermsRepository: save MemberTerms for each agreed term
    end
    
    MemberCommandService-->>MemberEventListener: return member
    MemberEventListener-->>Client: signup complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A signup with promises true,
Terms and conditions we review,
Each agreed one we persist with care,
Required terms must be there!
Hop along, new members, welcome anew! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main feature: adding mandatory terms agreement during signup, which directly reflects the primary changes in the changeset.
Description check ✅ Passed The description follows the template structure with all required sections completed: 변경사항, 관련 이슈, and 체크리스트 are present with relevant content filled in.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/165/Terms

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@gemini-code-assist
Copy link

Summary of Changes

Hello @chaechaen, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 사용자 회원가입 과정에 필수 약관 동의 기능을 통합하여, 사용자가 서비스 이용을 위한 필수 약관에 동의해야만 회원가입을 완료할 수 있도록 시스템을 강화합니다. 이를 통해 서비스의 법적 요구사항을 준수하고 사용자 동의 내역을 체계적으로 관리할 수 있게 됩니다.

Highlights

  • 회원가입 시 약관 동의 필수화: 회원가입 요청 시 필수 약관 동의 여부를 검증하고, 동의하지 않은 경우 회원가입을 제한하도록 로직이 추가되었습니다.
  • 약관 동의 정보 저장: 회원가입 시 사용자가 동의한 약관 ID 목록을 받아 Member 모듈로 전달하여, 회원 생성 후 해당 약관 동의 내역을 데이터베이스에 저장하도록 구현되었습니다.
  • 새로운 에러 상태 코드 추가: 약관 관련 예외 처리를 위한 새로운 에러 상태 코드(TERMS_NOT_FOUND, REQUIRED_TERMS_NOT_AGREE)가 MemberErrorStatus에 추가되었습니다.
  • 초기 약관 데이터 삽입: Flyway 마이그레이션 스크립트를 통해 서비스 이용을 위한 기본 약관 데이터가 데이터베이스에 삽입되었습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/main/java/checkmo/authentication/AuthenticationEvent.java
    • CreateMember 레코드에 'agreedTermsIds' 필드를 추가하여 동의한 약관 ID 목록을 포함하도록 변경되었습니다.
  • src/main/java/checkmo/authentication/internal/service/command/AuthUserCommandService.java
    • signUp 메서드에서 CreateMember 이벤트 발행 시 'agreedTermsIds'를 전달하도록 수정되었습니다.
    • 기존의 TODO 주석이 제거되었습니다.
  • src/main/java/checkmo/authentication/web/dto/AuthRequestDTO.java
    • SignUp DTO에 'agreedTermsIds' 필드가 추가되었으며, @NotEmpty 어노테이션을 통해 필수 값임을 검증하도록 설정되었습니다.
  • src/main/java/checkmo/member/internal/exception/MemberErrorStatus.java
    • 약관 관련 에러 상태 코드인 'TERMS_NOT_FOUND'와 'REQUIRED_TERMS_NOT_AGREE'가 추가되었습니다.
  • src/main/java/checkmo/member/internal/listener/MemberEventListener.java
    • createMember 이벤트 리스너가 memberCommandService.createMember 호출 시 'agreedTermsIds'를 함께 전달하도록 변경되었습니다.
  • src/main/java/checkmo/member/internal/repository/MemberTermsRepository.java
    • MemberTerms 엔티티를 위한 JpaRepository 인터페이스가 새로 추가되었습니다.
  • src/main/java/checkmo/member/internal/repository/TermsRepository.java
    • Terms 엔티티를 위한 JpaRepository 인터페이스가 새로 추가되었습니다.
  • src/main/java/checkmo/member/internal/service/command/MemberCommandService.java
    • MemberTermsRepository와 TermsRepository가 주입되었습니다.
    • createMember 메서드의 시그니처가 'agreedTermsIds'를 받도록 변경되었습니다.
    • 필수 약관 동의 여부를 검증하는 'validateRequiredTerms' 메서드가 추가되었습니다.
    • 회원 생성 후 동의한 약관 ID 목록을 기반으로 MemberTerms 엔티티를 생성하고 저장하는 로직이 추가되었습니다.
  • src/main/resources/db/migration/V20260211__insert_default_terms.sql
    • 초기 약관 데이터를 'terms' 테이블에 삽입하는 Flyway 마이그레이션 스크립트가 추가되었습니다. 여기에는 필수 약관 1개와 선택 약관 2개가 포함됩니다.
Activity
  • chaechaen님이 '[Feat] 회원가입 시 필수 약관 동의 추가' 제목으로 풀 리퀘스트를 생성했습니다.
  • 회원가입 시 필수 약관 동의 저장 로직 추가 (1번 필수 약관 동의 안 하면 회원가입 못함)에 대한 변경사항이 설명되었습니다.
  • 로컬 테스트 완료 및 코드 리뷰 준비 완료 체크리스트가 제시되었습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@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

🤖 Fix all issues with AI agents
In `@src/main/java/checkmo/authentication/AuthenticationEvent.java`:
- Around line 8-10: The CreateMember event built in CustomOAuth2UserService
omits agreedTermsIds, causing MemberCommandService.createMember's validation (if
(agreedTermsIds == null || !agreedTermsIds.containsAll(requiredTermsIds))) to
throw; fix by supplying a non-null list when constructing
CustomOAuth2UserService.CreateMember (either the actual agreed terms if social
login should require them or an explicit empty list like
Collections.emptyList()/List.of() to skip validation), updating the builder call
in CustomOAuth2UserService to set agreedTermsIds accordingly so
MemberCommandService.createMember receives a non-null value.

In
`@src/main/java/checkmo/member/internal/service/command/MemberCommandService.java`:
- Around line 38-39: In createMember, deduplicate the incoming agreedTermsIds
immediately (e.g., produce a distinctTermsIds collection) and use that for
validation and all subsequent logic so you don't create duplicate MemberTerms
rows; call validateRequiredTerms(distinctTermsIds) (instead of
validateRequiredTerms(agreedTermsIds)) and replace further usages of
agreedTermsIds in MemberTerms creation and related code with distinctTermsIds to
ensure each term results in at most one MemberTerms entry.

In `@src/main/resources/db/migration/V20260211__insert_default_terms.sql`:
- Around line 1-4: Add a trailing newline to the migration file so it ends with
a newline character and ensure the INSERT INTO terms statement is terminated
properly; additionally, if your project prefers not to hardcode PKs, remove the
explicit id values from the INSERT INTO terms (...) VALUES (...) (or adjust to
use DEFAULT for id) so GenerationType.IDENTITY/AUTO_INCREMENT manages IDs, or
add a sequence-reset statement after inserting fixed rows to keep auto-increment
in sync — update the INSERT INTO terms and any post-insert sequence maintenance
accordingly.
🧹 Nitpick comments (3)
src/main/java/checkmo/authentication/web/dto/AuthRequestDTO.java (1)

29-31: Consider adding @Schema annotation for API documentation consistency.

Other fields in this DTO have @Schema annotations for Swagger docs. Adding one here (e.g., @Schema(description = "동의한 약관 ID 목록", example = "[1, 2]")) would keep the API docs consistent.

src/main/java/checkmo/member/internal/service/command/MemberCommandService.java (2)

54-65: N+1 queries: batch-fetch terms and batch-save MemberTerms.

Each iteration issues a separate findById and save. For n agreed terms this means 2n queries. Fetch all agreed terms in one call and use saveAll.

♻️ Proposed refactor
-        // 여기서 약관 내역 DB에 저장 (멤버 생성 후)
-        for (Long termsId : agreedTermsIds) {
-            Terms terms = termsRepository.findById(termsId)
-                                         .orElseThrow(() -> new MemberException(MemberErrorStatus.TERMS_NOT_FOUND));
-
-            MemberTerms memberTerms = MemberTerms.builder()
-                                                 .member(member)
-                                                 .terms(terms)
-                                                 .isAgreed(true)
-                                                 .build();
-
-            memberTermsRepository.save(memberTerms);
-        }
+        List<Terms> termsList = termsRepository.findAllById(agreedTermsIds);
+        if (termsList.size() != agreedTermsIds.size()) {
+            throw new MemberException(MemberErrorStatus.TERMS_NOT_FOUND);
+        }
+
+        List<MemberTerms> memberTermsList = termsList.stream()
+                .map(terms -> MemberTerms.builder()
+                        .member(member)
+                        .terms(terms)
+                        .isAgreed(true)
+                        .build())
+                .toList();
+
+        memberTermsRepository.saveAll(memberTermsList);

68-77: Two concerns: findAll() is overly broad, and duplicate IDs are not guarded.

  1. findAll() loads every terms row just to filter required ones. Add a repository method like findByIsRequiredTrue() (or a @Query selecting where required = true) to push the filter to the DB.
  2. If agreedTermsIds contains duplicates, containsAll still passes but duplicate MemberTerms rows will be persisted. Deduplicate early with new ArrayList<>(new HashSet<>(agreedTermsIds)) or add a unique constraint on (member, terms).
♻️ Proposed refactor for validation
     private void validateRequiredTerms(List<Long> agreedTermsIds) {
-        List<Long> requiredTermsIds = termsRepository.findAll().stream()
-                                                     .filter(Terms::isRequired)
-                                                     .map(Terms::getId)
-                                                     .toList();
+        List<Long> requiredTermsIds = termsRepository.findByIsRequiredTrue().stream()
+                                                     .map(Terms::getId)
+                                                     .toList();

-        if (agreedTermsIds == null || !agreedTermsIds.containsAll(requiredTermsIds)) {
+        if (agreedTermsIds == null || !new HashSet<>(agreedTermsIds).containsAll(requiredTermsIds)) {
             throw new MemberException(MemberErrorStatus.REQUIRED_TERMS_NOT_AGREE);
         }
     }

Add to TermsRepository:

List<Terms> findByIsRequiredTrue();

Comment on lines 8 to 10
@Builder
public record CreateMember(String id, String email) {
public record CreateMember(String id, String email, List<Long> agreedTermsIds) {
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "CreateMember" --type java -C3

Repository: checkmo2025/BE

Length of output: 3426


🏁 Script executed:

rg -n "memberCommandService.createMember|createMember\(" --type java -C5 src/main/java/checkmo/member/

Repository: checkmo2025/BE

Length of output: 2314


🏁 Script executed:

rg -n "validateRequiredTerms" --type java -A10 src/main/java/checkmo/member/

Repository: checkmo2025/BE

Length of output: 2758


Fix social login signup: supply agreedTermsIds in CustomOAuth2UserService.CreateMember builder.

Social login flow in CustomOAuth2UserService (line 56) publishes CreateMember without agreedTermsIds, causing it to be null. The event listener then calls MemberCommandService.createMember(), which validates required terms at line 74: if (agreedTermsIds == null || !agreedTermsIds.containsAll(requiredTermsIds)) throws MemberException. This breaks the social login signup flow. Either supply the agreed terms list (if social login should require terms agreement) or an empty list (if social login should skip this validation).

🤖 Prompt for AI Agents
In `@src/main/java/checkmo/authentication/AuthenticationEvent.java` around lines 8
- 10, The CreateMember event built in CustomOAuth2UserService omits
agreedTermsIds, causing MemberCommandService.createMember's validation (if
(agreedTermsIds == null || !agreedTermsIds.containsAll(requiredTermsIds))) to
throw; fix by supplying a non-null list when constructing
CustomOAuth2UserService.CreateMember (either the actual agreed terms if social
login should require them or an explicit empty list like
Collections.emptyList()/List.of() to skip validation), updating the builder call
in CustomOAuth2UserService to set agreedTermsIds accordingly so
MemberCommandService.createMember receives a non-null value.

Comment on lines +38 to +39
public void createMember(String memberId, String email, List<Long> agreedTermsIds) {
validateRequiredTerms(agreedTermsIds);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider deduplicating agreedTermsIds at entry to prevent duplicate MemberTerms rows.

If the caller passes [1, 1, 2], two MemberTerms rows are created for terms 1. Dedup early:

-    public void createMember(String memberId, String email, List<Long> agreedTermsIds) {
-        validateRequiredTerms(agreedTermsIds);
+    public void createMember(String memberId, String email, List<Long> agreedTermsIds) {
+        List<Long> distinctTermsIds = agreedTermsIds != null
+                ? agreedTermsIds.stream().distinct().toList()
+                : null;
+        validateRequiredTerms(distinctTermsIds);

Then use distinctTermsIds for the rest of the method.

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

Suggested change
public void createMember(String memberId, String email, List<Long> agreedTermsIds) {
validateRequiredTerms(agreedTermsIds);
public void createMember(String memberId, String email, List<Long> agreedTermsIds) {
List<Long> distinctTermsIds = agreedTermsIds != null
? agreedTermsIds.stream().distinct().toList()
: null;
validateRequiredTerms(distinctTermsIds);
🤖 Prompt for AI Agents
In
`@src/main/java/checkmo/member/internal/service/command/MemberCommandService.java`
around lines 38 - 39, In createMember, deduplicate the incoming agreedTermsIds
immediately (e.g., produce a distinctTermsIds collection) and use that for
validation and all subsequent logic so you don't create duplicate MemberTerms
rows; call validateRequiredTerms(distinctTermsIds) (instead of
validateRequiredTerms(agreedTermsIds)) and replace further usages of
agreedTermsIds in MemberTerms creation and related code with distinctTermsIds to
ensure each term results in at most one MemberTerms entry.

Comment on lines +1 to +4
INSERT INTO terms (id, term_url, is_required) VALUES
(1, 'https://www.notion.so/2cd7445c73f780dab5c5ccfbf5ce6101', true), -- 서비스 이용을 위한 필수 개인정보 수집 이용 동의(필수)
(2, 'https://www.notion.so/2cd7445c73f780e3a653ead96321331f', false), -- 마케팅 및 이벤트 정보 수신 동의 (선택)
(3, 'https://www.notion.so/3-2cd7445c73f7806f9b37f75eea6acb30', false); -- 개인정보 제3자 제공 동의(선택) No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and check Terms entity for ID generation strategy
find . -type f -name "Terms.java" | head -5 | xargs -I {} sh -c 'echo "=== File: {} ===" && cat -n {}'

Repository: checkmo2025/BE

Length of output: 1048


🏁 Script executed:

#!/bin/bash
# Find CREATE TABLE migration for terms
rg -n "CREATE TABLE" --type sql -A10 | grep -A10 "terms"

Repository: checkmo2025/BE

Length of output: 2687


🏁 Script executed:

#!/bin/bash
# Check the exact last character(s) of the V20260211 file to confirm newline
hexdump -C src/main/resources/db/migration/V20260211__insert_default_terms.sql | tail -3

Repository: checkmo2025/BE

Length of output: 103


🏁 Script executed:

#!/bin/bash
# Check trailing newline using od
tail -c 20 src/main/resources/db/migration/V20260211__insert_default_terms.sql | od -c

Repository: checkmo2025/BE

Length of output: 100


🏁 Script executed:

#!/bin/bash
# Check if other seed data migrations also use explicit IDs
rg -n "INSERT INTO" --type sql -A1 src/main/resources/db/migration/ | head -40

Repository: checkmo2025/BE

Length of output: 966


🏁 Script executed:

#!/bin/bash
# Check trailing newline using tail with -c and wc
file="src/main/resources/db/migration/V20260211__insert_default_terms.sql"
last_char=$(tail -c 1 "$file" | wc -l)
echo "Lines at end: $last_char"
# Also show last few characters using sed
echo "Last 5 chars:"
tail -c 5 "$file" | sed 's/.$/[END]/g' | cat -A

Repository: checkmo2025/BE

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Check all INSERT migrations to see if hardcoded IDs are the standard pattern
find src/main/resources/db/migration -name "*.sql" -type f -exec grep -l "INSERT INTO" {} \; | while read f; do
  echo "=== $f ===" 
  grep -A2 "INSERT INTO" "$f" | head -6
done

Repository: checkmo2025/BE

Length of output: 863


Add trailing newline to the file; reconsider hardcoded IDs if sequence management is a project concern.

The file is missing a trailing newline (line 4 ends without one).

Regarding the hardcoded IDs (1, 2, 3): The Terms entity uses GenerationType.IDENTITY with AUTO_INCREMENT on the id column. Explicitly inserting IDs can technically advance the sequence; however, for static reference data like these three terms, hardcoded IDs are a standard pattern to ensure consistency across environments. This is only problematic if the application later inserts Terms via JPA without specifying IDs and sequence management becomes inconsistent. If your project avoids explicit IDs in seed migrations, consider using IDENTITY generation without pre-set values or resetting the sequence after insertion.

🤖 Prompt for AI Agents
In `@src/main/resources/db/migration/V20260211__insert_default_terms.sql` around
lines 1 - 4, Add a trailing newline to the migration file so it ends with a
newline character and ensure the INSERT INTO terms statement is terminated
properly; additionally, if your project prefers not to hardcode PKs, remove the
explicit id values from the INSERT INTO terms (...) VALUES (...) (or adjust to
use DEFAULT for id) so GenerationType.IDENTITY/AUTO_INCREMENT manages IDs, or
add a sequence-reset statement after inserting fixed rows to keep auto-increment
in sync — update the INSERT INTO terms and any post-insert sequence maintenance
accordingly.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 회원가입 시 필수 약관 동의 기능을 추가하는 변경사항을 담고 있습니다. 하지만 MemberCommandService에서 높은 심각도의 서비스 거부(DoS) 취약점이 발견되었습니다. 현재 구현은 사용자가 제공하는 많은 수의 약관 동의 ID로 인해 과도한 데이터베이스 쿼리를 유발할 수 있으며, 이는 데이터베이스를 과부하시켜 애플리케이션 가용성에 영향을 줄 수 있습니다. 또한, 약관 데이터 처리 과정에서 발생할 수 있는 N+1 문제와 비효율적인 데이터 필터링 및 검증 로직에 대한 성능 개선점이 있습니다. 이 문제들을 해결하면 더 안정적이고 확장성 있는 코드가 될 것입니다.

Comment on lines +54 to +65
for (Long termsId : agreedTermsIds) {
Terms terms = termsRepository.findById(termsId)
.orElseThrow(() -> new MemberException(MemberErrorStatus.TERMS_NOT_FOUND));

MemberTerms memberTerms = MemberTerms.builder()
.member(member)
.terms(terms)
.isAgreed(true)
.build();

memberTermsRepository.save(memberTerms);
}

Choose a reason for hiding this comment

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

security-high high

이 코드는 서비스 거부(DoS) 공격에 취약합니다. createMember 메서드는 사용자 제어 하에 있는 agreedTermsIds 목록을 반복하며 각 ID에 대해 데이터베이스 쿼리를 수행합니다. 공격자가 매우 많은 수의 ID를 제공하면 과도한 데이터베이스 쿼리가 발생하여 데이터베이스 리소스가 고갈되고 애플리케이션이 다른 사용자에게 제공되지 않을 수 있습니다. 현재 약관 동의 내역을 저장하는 로직은 동의한 약관의 수만큼 데이터베이스 조회가 발생하여 N+1 문제가 발생할 수 있습니다. findAllByIdsaveAll을 사용하여 여러 약관을 한 번에 조회하고 저장하는 것이 성능에 더 효율적입니다.

        List<Terms> allTerms = termsRepository.findAllById(agreedTermsIds);
        if (allTerms.size() != agreedTermsIds.size()) {
            throw new MemberException(MemberErrorStatus.TERMS_NOT_FOUND);
        }

        List<MemberTerms> memberTermsToSave = allTerms.stream()
            .map(term -> MemberTerms.builder()
                .member(member)
                .terms(term)
                .isAgreed(true)
                .build())
            .collect(Collectors.toList());

        memberTermsRepository.saveAll(memberTermsToSave);

Comment on lines +69 to +76
List<Long> requiredTermsIds = termsRepository.findAll().stream()
.filter(Terms::isRequired)
.map(Terms::getId)
.toList();

if (agreedTermsIds == null || !agreedTermsIds.containsAll(requiredTermsIds)) {
throw new MemberException(MemberErrorStatus.REQUIRED_TERMS_NOT_AGREE);
}

Choose a reason for hiding this comment

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

high

필수 약관을 검증하는 로직이 비효율적입니다.

  1. termsRepository.findAll()은 모든 약관을 DB에서 조회하므로 약관이 많아지면 성능 저하를 유발할 수 있습니다. TermsRepositoryfindAllByIsRequired(true)와 같은 메서드를 추가하여 DB에서 직접 필터링하는 것이 좋습니다.
  2. List.containsAll은 시간 복잡도가 O(n*m)으로 비효율적입니다. agreedTermsIdsHashSet으로 변환하여 containsAll을 호출하면 평균 시간 복잡도를 O(n+m)으로 개선할 수 있습니다.
        List<Long> requiredTermsIds = termsRepository.findAllByIsRequired(true).stream()
                                                     .map(Terms::getId)
                                                     .toList();

        if (agreedTermsIds == null || !new HashSet<>(agreedTermsIds).containsAll(requiredTermsIds)) {
            throw new MemberException(MemberErrorStatus.REQUIRED_TERMS_NOT_AGREE);
        }

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.

[FEAT] 약관 저장

1 participant