Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -288,5 +288,26 @@ public ResponseEntity<ApiResponse<NicknameCheckResponseDTO>> checkNicknameDuplic
NicknameCheckResponseDTO nicknameCheckResponseDTO = memberService.checkNicknameDuplicate(nickname);
return ApiResponse.success(SuccessStatus.CHECK_NICKNAME_DUPLICATE_SUCCESS, nicknameCheckResponseDTO);
}

/*
*
* 약관동의 API
*
* */
@Operation(
summary = "이용약관동의 API",
description = "이용약관 동의 여부를 저장합니다."
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "닉네임 중복 체크 성공")
})
@PostMapping("/agree-terms")
public ResponseEntity<ApiResponse<Void>> agreeToTerms(
@AuthenticationPrincipal UserDetails userDetails,
@RequestBody AgreeTermsRequestDTO agreeTermsRequestDTO) {

memberService.agreeToTerms(userDetails.getUsername(), agreeTermsRequestDTO);
return ApiResponse.success_only(SuccessStatus.TERMS_AGREE_SUCCESS);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.moongeul.backend.api.member.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AgreeTermsRequestDTO {

private boolean serviceTermsAgree;
private boolean privatePolicyAgree;
private boolean marketingAgree;
}
35 changes: 35 additions & 0 deletions src/main/java/com/moongeul/backend/api/member/entity/Agree.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.moongeul.backend.api.member.entity;

import com.moongeul.backend.common.entity.BaseTimeEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder // 빌더 패턴 사용을 위한 롬복 애너테이션
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드를 포함한 생성자
@Table(name = "AGREE") // 데이터베이스 테이블 이름 지정
public class Agree extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "terms_id", nullable = false)
private Terms terms;

private boolean isAgreed; // 동의 여부 (true/false)

public void updateAgreement(boolean isAgreed) {
this.isAgreed = isAgreed;
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/moongeul/backend/api/member/entity/Terms.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.moongeul.backend.api.member.entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Builder // 빌더 패턴 사용을 위한 롬복 애너테이션
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드를 포함한 생성자
@Table(name = "TERMS") // 데이터베이스 테이블 이름 지정
public class Terms {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Enumerated(EnumType.STRING)
private TermsType termsType; // 이용약관 타입

private String version; //v1.0, v1.1, v2.0 ...
private String content; // 약관 조항
private boolean isRequired; // 필수 여부
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.moongeul.backend.api.member.entity;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum TermsType {

SERVICE_TERMS_AGREE("서비스 이용약관 동의"),
PRIVACY_POLICY_AGREE("개인 정보 수집 및 이용 동의"),
MARKETING_AGREE("마케팅 정보 수신 동의");

private final String key;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.moongeul.backend.api.member.repository;

import com.moongeul.backend.api.member.entity.Agree;
import com.moongeul.backend.api.member.entity.Member;
import com.moongeul.backend.api.member.entity.Terms;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface AgreeRepository extends JpaRepository<Agree, Long> {

Optional<Agree> findByMemberAndTerms(Member member, Terms terms);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.moongeul.backend.api.member.repository;

import com.moongeul.backend.api.member.entity.Terms;
import com.moongeul.backend.api.member.entity.TermsType;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface TermsRepository extends JpaRepository<Terms, Long> {

Optional<Terms> findByTermsType(TermsType type);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
import com.moongeul.backend.api.category.entity.Category;
import com.moongeul.backend.api.category.repository.CategoryRepository;
import com.moongeul.backend.api.member.dto.*;
import com.moongeul.backend.api.member.entity.Follow;
import com.moongeul.backend.api.member.entity.FollowStatus;
import com.moongeul.backend.api.member.entity.Member;
import com.moongeul.backend.api.member.entity.PrivacyLevel;
import com.moongeul.backend.api.member.entity.Role;
import com.moongeul.backend.api.member.entity.*;
import com.moongeul.backend.api.member.jwt.dto.JwtTokenDTO;
import com.moongeul.backend.api.member.repository.AgreeRepository;
import com.moongeul.backend.api.member.repository.FollowRepository;
import com.moongeul.backend.api.member.repository.MemberRepository;
import com.moongeul.backend.api.member.repository.TermsRepository;
import com.moongeul.backend.api.member.util.NicknameGenerator;
import com.moongeul.backend.api.post.dto.CategoryPostListResponseDTO;
import com.moongeul.backend.api.post.dto.PostDTO;
Expand All @@ -33,9 +31,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;

@Service
Expand All @@ -52,6 +48,8 @@ public class MemberService {
private final GoogleOAuthService googleOAuthService;
private final KakaoOAuthService kakaoOAuthService;
private final NicknameGenerator nicknameGenerator;
private final TermsRepository termsRepository;
private final AgreeRepository agreeRepository;

// 인가코드 받아 JWT로 교환 및 회원가입/로그인 처리
@Transactional
Expand Down Expand Up @@ -223,11 +221,6 @@ public PostStatsResponseDTO getPostStats(String email, Long userId) {
.build();
}

private Member getMemberByEmail(String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new NotFoundException(ErrorStatus.USER_NOTFOUND_EXCEPTION.getMessage()));
}

@Transactional
public JwtTokenDTO reissueToken(String refreshToken){

Expand Down Expand Up @@ -428,4 +421,55 @@ private void validatePrivacyAccess(Member currentMember, Member targetMember) {
}
}
}

/*
*
* 약관동의 API
*
* */
@Transactional
public void agreeToTerms(String email, AgreeTermsRequestDTO agreeTermsRequestDTO){

Member member = getMemberByEmail(email);

// 예외처리: 필수 동의 여부 검증
if (!agreeTermsRequestDTO.isServiceTermsAgree() || !agreeTermsRequestDTO.isPrivatePolicyAgree()) {
throw new BadRequestException(ErrorStatus.DISAGREE_REQUIRED_TERM.getMessage());
}

// 1. 약관 타입과 DTO의 동의 여부 매핑
Map<TermsType, Boolean> agreementData = Map.of(
TermsType.SERVICE_TERMS_AGREE, agreeTermsRequestDTO.isServiceTermsAgree(),
TermsType.PRIVACY_POLICY_AGREE, agreeTermsRequestDTO.isPrivatePolicyAgree(),
TermsType.MARKETING_AGREE, agreeTermsRequestDTO.isMarketingAgree()
);

agreementData.forEach((type, isAgreed) -> {
// 1. 해당 타입의 약관 마스터 정보 조회
Terms terms = termsRepository.findByTermsType(type)
.orElseThrow(() -> new NotFoundException(ErrorStatus.TERMS_NOTFOUND_EXCEPTION.getMessage()));

// 2. 기존 동의 내역이 있는지 조회
agreeRepository.findByMemberAndTerms(member, terms)
.ifPresentOrElse(
// 이미 데이터가 있다면? -> 동의 여부 필드만 수정 (Dirty Checking 발생)
existingAgree -> existingAgree.updateAgreement(isAgreed),
// 데이터가 없다면? -> 새로 생성해서 저장
() -> {
Agree newAgree = Agree.builder()
.member(member)
.terms(terms)
.isAgreed(isAgreed)
.build();
agreeRepository.save(newAgree);
}
);
});
}

// 회원 조회 메서드
private Member getMemberByEmail(String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new NotFoundException(ErrorStatus.USER_NOTFOUND_EXCEPTION.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public enum ErrorStatus {
INVALID_ANSWER_VALUE(HttpStatus.BAD_REQUEST, "테스트 답변은 A 또는 B여야 합니다. (질문번호: %d, 현재답변: %s)"),
NO_SCORE_RESULT(HttpStatus.BAD_REQUEST, "테스트 점수 계산 결과가 없습니다."),
REVIEW_UNAUTHORIZED(HttpStatus.BAD_REQUEST, "수정하려는 회원의 리뷰가 아닙니다."),
DISAGREE_REQUIRED_TERM(HttpStatus.BAD_REQUEST, "필수 약관에 모두 동의해야 합니다."),

/**
* 401 UNAUTHORIZED
Expand All @@ -48,6 +49,7 @@ public enum ErrorStatus {
WEEKLY_RECOMMENDATION_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "주간 추천 기록을 찾을 수 없습니다."),
QUESTION_NOTFOUND_EXCEPTION(HttpStatus.NOT_FOUND, "해당 질문을 찾을 수 없습니다."),
ANSWER_NOTFOUND_EXCEPTION(HttpStatus.NOT_FOUND, "해당 답변을 찾을 수 없습니다."),
TERMS_NOTFOUND_EXCEPTION(HttpStatus.NOT_FOUND, "약관 정보를 찾을 수 없습니다."),

/**
* 400 BAD_REQUEST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public enum SuccessStatus {
CHECK_NICKNAME_DUPLICATE_SUCCESS(HttpStatus.OK, "닉네임 중복 체크 성공"),
GET_PRIVACY_LEVEL_SUCCESS(HttpStatus.OK, "계정 공개 범위 조회 성공"),
UPDATE_PRIVACY_LEVEL_SUCCESS(HttpStatus.OK, "계정 공개 범위 수정 성공"),
TERMS_AGREE_SUCCESS(HttpStatus.OK, "약관 동의 성공"),

/* BOOK */
SEARCH_BOOK_SUCCESS(HttpStatus.OK, "도서 검색 성공"),
Expand Down