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
@@ -0,0 +1,38 @@
package backend.greatjourney.domain.terms.controller;

import backend.greatjourney.domain.terms.dto.TermsAgreementRequest;
import backend.greatjourney.domain.terms.service.TermsAgreementService;
import backend.greatjourney.global.exception.BaseResponse;
import backend.greatjourney.global.security.entitiy.CustomOAuth2User;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/terms-agreement")
@RequiredArgsConstructor
public class TermsAgreementController {

private final TermsAgreementService termsAgreementService;

@PostMapping
public ResponseEntity<BaseResponse<String>> agreeToTerms(
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
@RequestBody TermsAgreementRequest request) {

termsAgreementService.agreeToTerms(customOAuth2User, request);

return ResponseEntity.ok(
BaseResponse.<String>builder()
.isSuccess(true)
.code(200)
.message("약관 동의가 성공적으로 처리되었습니다.")
.data(null)
.build()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package backend.greatjourney.domain.terms.controller;

import backend.greatjourney.domain.terms.domain.TermsType;
import backend.greatjourney.domain.terms.dto.TermsResponseDto;
import backend.greatjourney.domain.terms.service.TermsService;
import backend.greatjourney.global.exception.BaseResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/terms")
@RequiredArgsConstructor
public class TermsController {

private final TermsService termsService;

@GetMapping
public ResponseEntity<BaseResponse<TermsResponseDto>> getTerms(
@RequestParam("type") TermsType type) {

TermsResponseDto termsData = termsService.getLatestTerms(type);

return ResponseEntity.ok(
BaseResponse.<TermsResponseDto>builder()
.isSuccess(true)
.code(200)
.message(type.name() + " 약관 조회에 성공했습니다.")
.data(termsData)
.build()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package backend.greatjourney.domain.terms.domain;


import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.joda.time.LocalDate;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Terms {

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

@Enumerated(EnumType.STRING) // Enum 이름을 DB에 문자열로 저장
@Column(nullable = false)
private TermsType type; // 약관 종류

@Column(nullable = false)
private String version; // 버전 (예: "1.0", "1.1")

@Lob // CLOB, TEXT 등 DB의 대용량 텍스트 타입과 매핑
@Column(nullable = false, columnDefinition = "TEXT")
private String content; // 약관 내용 (HTML)

@Column(nullable = false)
private boolean isRequired; // 필수 동의 여부

private String effectiveDate;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package backend.greatjourney.domain.terms.domain;

public enum TermsType {
TERMS_OF_SERVICE, // 서비스 이용약관
PRIVACY_POLICY, // 개인정보 처리방침
MARKETING_AGREEMENT // 마케팅 정보 수신 동의 (선택)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package backend.greatjourney.domain.terms.domain;


import backend.greatjourney.domain.user.entity.User;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class) // @CreatedDate 자동 생성을 위해 필요
public class UserTermsAgreement {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

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

@CreatedDate // 엔티티가 생성될 때 시간이 자동으로 저장됩니다.
@Column(nullable = false, updatable = false)
private LocalDateTime agreedAt; // 동의한 시간

@Builder
public UserTermsAgreement(User user, Terms terms) {
this.user = user;
this.terms = terms;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package backend.greatjourney.domain.terms.dto;

// 약관 ID 목록을 받는 간단한 record DTO
public record TermsAgreementRequest(
java.util.List<Long> agreedTermsIds
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package backend.greatjourney.domain.terms.dto;

import backend.greatjourney.domain.terms.domain.Terms;
import backend.greatjourney.domain.terms.domain.TermsType;

public record TermsResponseDto(
TermsType type,
String version,
String content,
boolean isRequired,
String effectiveDate
) {
public static TermsResponseDto from(Terms terms) {
return new TermsResponseDto(
terms.getType(),
terms.getVersion(),
terms.getContent(),
terms.isRequired(),
terms.getEffectiveDate()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package backend.greatjourney.domain.terms.repository;


import backend.greatjourney.domain.terms.domain.Terms;
import backend.greatjourney.domain.terms.domain.TermsType;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface TermsRepository extends JpaRepository<Terms, Long> {
// 특정 타입의 약관 중 가장 최신 버전을 조회 (적용일과 버전으로 정렬)
Optional<Terms> findFirstByTypeOrderByEffectiveDateDescVersionDesc(TermsType type);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package backend.greatjourney.domain.terms.repository;

import backend.greatjourney.domain.terms.domain.UserTermsAgreement;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserTermsAgreementRepository extends JpaRepository<UserTermsAgreement, Long> {
// 필요시 특정 유저의 동의 내역을 찾는 메서드 등을 추가할 수 있습니다.
// boolean existsByUserAndTerms(User user, Terms terms);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package backend.greatjourney.domain.terms.service;


import backend.greatjourney.domain.terms.domain.Terms;
import backend.greatjourney.domain.terms.domain.UserTermsAgreement;
import backend.greatjourney.domain.terms.dto.TermsAgreementRequest;
import backend.greatjourney.domain.terms.repository.TermsRepository;
import backend.greatjourney.domain.terms.repository.UserTermsAgreementRepository;
import backend.greatjourney.domain.user.entity.User;
import backend.greatjourney.domain.user.repository.UserRepository;
import backend.greatjourney.global.exception.CustomException;
import backend.greatjourney.global.exception.ErrorCode;
import backend.greatjourney.global.security.entitiy.CustomOAuth2User;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class TermsAgreementService {

private final UserRepository userRepository;
private final TermsRepository termsRepository;
private final UserTermsAgreementRepository userTermsAgreementRepository;

public void agreeToTerms(CustomOAuth2User customOAuth2User, TermsAgreementRequest request) {
Long userId = Long.parseLong(customOAuth2User.getUserId());
User user = userRepository.findByUserId(userId)
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

// 요청받은 ID 목록으로 모든 Terms 엔티티를 조회
List<Terms> termsToAgree = termsRepository.findAllById(request.agreedTermsIds());

// 요청 ID 개수와 실제 조회된 엔티티 개수가 다르면 잘못된 ID가 포함된 것
if (termsToAgree.size() != request.agreedTermsIds().size()) {
throw new CustomException(ErrorCode.TERMS_NOT_FOUND); // 혹은 다른 적절한 에러 코드
}

// 각 약관에 대해 동의 내역을 생성하고 저장
List<UserTermsAgreement> agreements = termsToAgree.stream()
.map(term -> UserTermsAgreement.builder()
.user(user)
.terms(term)
.build())
.toList();

userTermsAgreementRepository.saveAll(agreements);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package backend.greatjourney.domain.terms.service;

import backend.greatjourney.domain.terms.domain.TermsType;
import backend.greatjourney.domain.terms.dto.TermsResponseDto;
import backend.greatjourney.domain.terms.repository.TermsRepository;
import backend.greatjourney.global.exception.CustomException;
import backend.greatjourney.global.exception.ErrorCode;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Transactional
public class TermsService {

private final TermsRepository termsRepository;

public TermsResponseDto getLatestTerms(TermsType type) {
return termsRepository.findFirstByTypeOrderByEffectiveDateDescVersionDesc(type)
.map(TermsResponseDto::from)
.orElseThrow(() -> new CustomException(ErrorCode.TERMS_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public enum ErrorCode {

CERTIFICATION_CENTER_NOT_FOUND(HttpStatus.BAD_REQUEST, 400, "조건에 맞는 인증센터를 찾을 수 없습니다."),

PLACE_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "해당 장소를 찾을 수 없습니다.")

PLACE_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "해당 장소를 찾을 수 없습니다."),
TERMS_NOT_FOUND(HttpStatus.NOT_FOUND, 404, "요청한 종류의 약관을 찾을 수 없습니다.")


;
Expand Down