Skip to content

Conversation

@slg1119
Copy link
Contributor

@slg1119 slg1119 commented Nov 4, 2025

작업 내용 ⚒️

  • 약관 동의 저장 기능 추가

리뷰어 참고 사항 🤔

  • 없음

DDL

-- 없음

@slg1119 slg1119 requested a review from Copilot November 4, 2025 14:05
@slg1119 slg1119 self-assigned this Nov 4, 2025
@slg1119 slg1119 added the ✨ Feat 기능 개발 label Nov 4, 2025
@gemini-code-assist
Copy link

Summary of Changes

Hello @slg1119, 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!

This pull request introduces a new feature to handle user's terms and conditions agreement. It includes adding a new controller, service, repository, and DTOs to manage terms agreement. Also, it modifies the security configuration to allow public access to the terms endpoint and adds logic to delete terms agreement information upon user withdrawal.

Highlights

  • 약관 동의 저장 기능 추가: 사용자가 약관에 동의한 정보를 저장하고, 필요시 조회할 수 있는 기능이 추가되었습니다.
  • SecurityConfig 수정: /terms 엔드포인트에 대한 접근 권한을 permitAll()로 설정하여 인증 없이 접근 가능하도록 변경했습니다.
  • 회원 탈퇴 시 약관 동의 정보 삭제: 회원이 탈퇴할 때, 관련된 약관 동의 정보도 함께 삭제되도록 로직을 추가했습니다.
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.

@slg1119 slg1119 merged commit 58284c4 into dev Nov 4, 2025
8 checks passed
@slg1119 slg1119 deleted the feat/term branch November 4, 2025 14:08
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

The pull request introduces functionality to manage user terms agreements, including new API endpoints, service logic, and repository interfaces. It also integrates terms agreement deletion into the member withdrawal process and adds corresponding tests. The changes are well-structured and include appropriate validation and error handling.

.requestMatchers("/error").permitAll()
.requestMatchers("/error/**").permitAll()
.requestMatchers("/actuator/**").permitAll()
.requestMatchers("/terms").permitAll()

Choose a reason for hiding this comment

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

medium

The /terms endpoint is now publicly accessible. While /terms itself might be public for viewing general terms, /terms/me and /terms/me (POST) are protected by PreAuthorize. Ensure that the public access to /terms is intentional and does not expose any sensitive information or allow unauthorized actions, especially if there are plans to add more sub-paths under /terms that should be protected.


Optional<MemberTermsAgreement> findByMemberIdAndTermsId(UUID memberId, Long termsId);

void deleteAllByMemberId(UUID memberId);

Choose a reason for hiding this comment

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

medium

The deleteAllByMemberId method is used to remove all terms agreements for a given member. While this is functional, consider using a @Modifying query with @Query annotation for bulk deletion, which can be more efficient than fetching all entities and then deleting them one by one, especially for a large number of agreements. This can improve performance during member withdrawal.

Comment on lines +85 to +88
if (existingAgreement != null) {
// 기존 레코드가 있으면 삭제 후 새로 생성 (업데이트)
memberTermsAgreementRepository.delete(existingAgreement);
}

Choose a reason for hiding this comment

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

medium

The current implementation deletes an existing MemberTermsAgreement and then saves a new one to update it. This approach can lead to unnecessary database operations (delete then insert) and potential issues with database triggers or auditing if not handled carefully. A more efficient approach would be to update the isAgreed status of the existingAgreement directly if it exists, and only create a new one if it doesn't.

            if (existingAgreement != null) {
                existingAgreement.setIsAgreed(agreementRequest.getIsAgreed());
                memberTermsAgreementRepository.save(existingAgreement);
            } else {

Comment on lines +90 to +97
// 새로운 동의 정보 저장
MemberTermsAgreement newAgreement = MemberTermsAgreement.builder()
.memberId(memberId)
.termsId(agreementRequest.getTermId())
.isAgreed(agreementRequest.getIsAgreed())
.build();

memberTermsAgreementRepository.save(newAgreement);

Choose a reason for hiding this comment

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

medium

Following up on the previous comment, if an existing agreement is found, it should be updated instead of deleted and re-created. The else block should contain the creation of a new agreement only if existingAgreement is null.

            } else {
                // 새로운 동의 정보 저장
                MemberTermsAgreement newAgreement = MemberTermsAgreement.builder()
                        .memberId(memberId)
                        .termsId(agreementRequest.getTermId())
                        .isAgreed(agreementRequest.getIsAgreed())
                        .build();
                memberTermsAgreementRepository.save(newAgreement);
            }

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a comprehensive terms and conditions (T&C) management system, allowing users to view all terms, check their agreement status, and save/update their term agreements. The implementation includes REST API endpoints, service layer logic with validation, and data persistence.

Key Changes:

  • Added term management REST API endpoints with authentication and comprehensive API documentation
  • Implemented business logic for retrieving, validating, and persisting term agreements
  • Integrated term agreement deletion into member withdrawal flow

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
TermController.java Implements REST endpoints for retrieving terms and managing user agreements
TermService.java Service interface defining term management operations
TermServiceImpl.java Service implementation with validation logic and agreement persistence
TermRepository.java Data access layer for Term entity
TermDto.java DTOs for term requests and responses with validation annotations
MemberTermsAgreementRepository.java Data access layer for member term agreements
MemberServiceImpl.java Updated to delete term agreements on member withdrawal
SecurityConfig.java Configured public access to terms list endpoint
TermControllerTest.java Comprehensive controller integration tests with API documentation
TermServiceImplTest.java Service layer unit tests covering all business logic
MemberServiceImplTest.java Added test for term agreement deletion on withdrawal

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +86 to +97
// 기존 레코드가 있으면 삭제 후 새로 생성 (업데이트)
memberTermsAgreementRepository.delete(existingAgreement);
}

// 새로운 동의 정보 저장
MemberTermsAgreement newAgreement = MemberTermsAgreement.builder()
.memberId(memberId)
.termsId(agreementRequest.getTermId())
.isAgreed(agreementRequest.getIsAgreed())
.build();

memberTermsAgreementRepository.save(newAgreement);
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

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

The delete-then-insert pattern for updates is inefficient and may cause unnecessary database operations. Consider implementing an update method on the entity or using a single upsert operation. If the BaseEntity class provides timestamp tracking, the current approach also loses the original creation timestamp.

Suggested change
// 기존 레코드가 있으면 삭제 후 새로 생성 (업데이트)
memberTermsAgreementRepository.delete(existingAgreement);
}
// 새로운 동의 정보 저장
MemberTermsAgreement newAgreement = MemberTermsAgreement.builder()
.memberId(memberId)
.termsId(agreementRequest.getTermId())
.isAgreed(agreementRequest.getIsAgreed())
.build();
memberTermsAgreementRepository.save(newAgreement);
// 기존 레코드가 있으면 업데이트
existingAgreement.setIsAgreed(agreementRequest.getIsAgreed());
memberTermsAgreementRepository.save(existingAgreement);
} else {
// 새로운 동의 정보 저장
MemberTermsAgreement newAgreement = MemberTermsAgreement.builder()
.memberId(memberId)
.termsId(agreementRequest.getTermId())
.isAgreed(agreementRequest.getIsAgreed())
.build();
memberTermsAgreementRepository.save(newAgreement);
}

Copilot uses AI. Check for mistakes.
import java.time.LocalDateTime;
import java.util.List;
import kr.swyp.backend.authentication.provider.TokenProvider;
import kr.swyp.backend.common.desciptor.ErrorDescriptor;
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'desciptor' to 'descriptor'.

Suggested change
import kr.swyp.backend.common.desciptor.ErrorDescriptor;
import kr.swyp.backend.common.descriptor.ErrorDescriptor;

Copilot uses AI. Check for mistakes.
@gemini-code-assist
Copy link

Summary of Changes

Hello @slg1119, 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!

This pull request introduces a new feature to handle the storage and retrieval of user's terms and conditions agreements. It includes new API endpoints for managing terms agreement, a repository for storing the agreement information, and updates to the member withdrawal process to ensure terms agreement data is properly removed upon user's account deletion. Security configurations have also been updated to allow public access to the /terms endpoint.

Highlights

  • 약관 동의 저장 기능 추가: 사용자가 약관에 동의한 정보를 저장하고 조회할 수 있는 기능이 추가되었습니다.
  • API 엔드포인트 추가: /terms 엔드포인트를 통해 약관 목록 조회 및 동의 정보 저장/조회가 가능합니다.
  • 회원 탈퇴 시 약관 동의 정보 삭제: 회원이 탈퇴할 때, 관련된 약관 동의 정보도 함께 삭제되도록 기능이 추가되었습니다.
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

@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

전반적으로 약관 동의 저장 기능이 잘 구현되었습니다. 컨트롤러, 서비스, 레포지토리, DTO가 잘 구성되어 있고 테스트 코드도 충실하게 작성되었습니다. 다만, saveMemberTermsAgreements 서비스 메서드에서 약관 동의 정보를 저장하는 방식이 비효율적이며 잠재적인 버그를 유발할 수 있어 개선이 필요해 보입니다. 해당 부분에 대한 리뷰 코멘트를 남겼으니 확인 부탁드립니다.

Comment on lines +64 to +99
@Override
@Transactional
public void saveMemberTermsAgreements(UUID memberId, TermsAgreementRequest request) {
for (TermAgreementRequest agreementRequest : request.getAgreements()) {
// 약관 존재 여부 확인
Term term = termRepository.findById(agreementRequest.getTermId())
.orElseThrow(() -> new NoSuchElementException(
"약관을 찾을 수 없습니다. ID: " + agreementRequest.getTermId()));

// 필수 약관인데 동의하지 않은 경우 예외 발생
if (term.getIsRequired() && !agreementRequest.getIsAgreed()) {
throw new IllegalArgumentException(
"필수 약관에 동의해야 합니다: " + term.getTitle());
}

// 기존 동의 정보 조회
MemberTermsAgreement existingAgreement =
memberTermsAgreementRepository.findByMemberIdAndTermsId(
memberId, agreementRequest.getTermId()
).orElse(null);

if (existingAgreement != null) {
// 기존 레코드가 있으면 삭제 후 새로 생성 (업데이트)
memberTermsAgreementRepository.delete(existingAgreement);
}

// 새로운 동의 정보 저장
MemberTermsAgreement newAgreement = MemberTermsAgreement.builder()
.memberId(memberId)
.termsId(agreementRequest.getTermId())
.isAgreed(agreementRequest.getIsAgreed())
.build();

memberTermsAgreementRepository.save(newAgreement);
}
}

Choose a reason for hiding this comment

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

high

현재 약관 동의 저장 로직은 요청에 포함된 각 약관에 대해 데이터베이스 조회, 삭제, 저장을 반복하고 있습니다. 이 방식은 요청하는 약관의 수(N)에 비례하여 DB I/O가 증가하여(N번의 SELECT, N번의 DELETE, N번의 INSERT 가능성) 비효율적입니다.

또한, 이 로직은 요청에 포함되지 않은 기존 약관 동의 정보를 처리하지 않아, 사용자가 선택 약관 동의를 철회하는 시나리오(요청에서 해당 약관을 제외)에서 기존 동의가 삭제되지 않는 문제가 발생할 수 있습니다.

전체 약관 동의 상태를 한 번에 교체하는 방식으로 로직을 개선하는 것을 제안합니다. 즉, 특정 회원의 기존 약관 동의를 모두 삭제한 후, 요청받은 약관 동의 목록을 한 번에 저장하는 것입니다. 이렇게 하면 DB 접근 횟수를 크게 줄일 수 있고 로직이 명확해집니다.

    @Override
    @Transactional
    public void saveMemberTermsAgreements(UUID memberId, TermsAgreementRequest request) {
        List<TermAgreementRequest> agreementRequests = request.getAgreements();
        List<Long> termIds = agreementRequests.stream()
                .map(TermAgreementRequest::getTermId)
                .collect(Collectors.toList());

        // 1. 약관 유효성 검증
        Map<Long, Term> termsMap = termRepository.findAllById(termIds).stream()
                .collect(Collectors.toMap(Term::getId, t -> t));

        if (termsMap.size() != agreementRequests.size()) {
            // 요청된 약관 ID 중 존재하지 않는 것이 있음
            throw new NoSuchElementException("하나 이상의 약관을 찾을 수 없습니다.");
        }

        for (TermAgreementRequest agreementRequest : agreementRequests) {
            Term term = termsMap.get(agreementRequest.getTermId());
            if (term.getIsRequired() && !agreementRequest.getIsAgreed()) {
                throw new IllegalArgumentException("필수 약관에 동의해야 합니다: " + term.getTitle());
            }
        }

        // 2. 기존 동의 내역 일괄 삭제
        memberTermsAgreementRepository.deleteAllByMemberId(memberId);

        // 3. 새로운 동의 내역 일괄 저장
        List<MemberTermsAgreement> newAgreements = agreementRequests.stream()
                .map(agreementRequest -> MemberTermsAgreement.builder()
                        .memberId(memberId)
                        .termsId(agreementRequest.getTermId())
                        .isAgreed(agreementRequest.getIsAgreed())
                        .build())
                .collect(Collectors.toList());

        memberTermsAgreementRepository.saveAll(newAgreements);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feat 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants