diff --git a/src/main/java/com/aolda/itda/controller/certificate/CertificateController.java b/src/main/java/com/aolda/itda/controller/certificate/CertificateController.java new file mode 100644 index 0000000000000000000000000000000000000000..b6384c923076c6144efc770aa77a6e25a533cbec --- /dev/null +++ b/src/main/java/com/aolda/itda/controller/certificate/CertificateController.java @@ -0,0 +1,93 @@ +package com.aolda.itda.controller.certificate; + +import com.aolda.itda.dto.certificate.CertificateDTO; +import com.aolda.itda.service.certificate.CertificateService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class CertificateController { + + private final CertificateService certificateService; + + /** + * 인증서 생성 + * POST /api/certificate?projectId=xxx + * Body: CertificateDTO + */ + @PostMapping("/certificate") + public ResponseEntity<Object> create(@RequestParam String projectId, + @RequestBody CertificateDTO dto, + HttpServletRequest request) { + certificateService.createCertificate( + projectId, + dto, + (List<String>) request.getAttribute("projects") // Forwarding과 동일하게 + ); + return ResponseEntity.ok().build(); + } + + /** + * 인증서 단건 조회 + * GET /api/certificate?certificateId=xxx + */ + @GetMapping("/certificate") + public ResponseEntity<Object> view(@RequestParam Long certificateId, + HttpServletRequest request) { + return ResponseEntity.ok( + certificateService.getCertificate( + certificateId, + (List<String>) request.getAttribute("projects") + ) + ); + } + + /** + * 인증서 목록 조회 + * GET /api/certificates?projectId=xxx + */ + @GetMapping("/certificates") + public ResponseEntity<Object> lists(@RequestParam String projectId) { + return ResponseEntity.ok( + certificateService.getCertificates(projectId) + ); + } + + /** + * 인증서 수정 + * PATCH /api/certificate?certificateId=xxx + * Body: CertificateDTO + */ + @PatchMapping("/certificate") + public ResponseEntity<Object> edit(@RequestParam Long certificateId, + @RequestBody CertificateDTO dto, + HttpServletRequest request) { + certificateService.editCertificate( + certificateId, + dto, + (List<String>) request.getAttribute("projects") + ); + return ResponseEntity.ok().build(); + } + + /** + * 인증서 삭제 + * DELETE /api/certificate?certificateId=xxx + */ + @DeleteMapping("/certificate") + public ResponseEntity<Object> delete(@RequestParam Long certificateId, + HttpServletRequest request) { + certificateService.deleteCertificate( + certificateId, + (List<String>) request.getAttribute("projects") + ); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/com/aolda/itda/dto/certificate/CertificateDTO.java b/src/main/java/com/aolda/itda/dto/certificate/CertificateDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..35aad948a3c16fae5d8556ed46f9e57a192b19dc --- /dev/null +++ b/src/main/java/com/aolda/itda/dto/certificate/CertificateDTO.java @@ -0,0 +1,21 @@ +package com.aolda.itda.dto.certificate; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CertificateDTO { + + private Long certificateId; + private String projectId; + private String domain; + private LocalDateTime expiredAt; // 필요 시 + private Boolean isDeleted; + private String description; // 추가 설명 +} diff --git a/src/main/java/com/aolda/itda/entity/certificate/Certificate.java b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java index 85b1f29b492b15fa7e213d6f94ee8f683d55c61e..469ea6aefadf46dd1ce2789ca7aea720427a42ec 100644 --- a/src/main/java/com/aolda/itda/entity/certificate/Certificate.java +++ b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java @@ -49,4 +49,13 @@ public class Certificate extends BaseTimeEntity { public String formatDomain() { return domain == null ? null : domain.replace("*", "_"); } + + public void setIsDeleted(boolean b) { + } + + public void setDomain(String domain) { + } + + public void setDescription(String description) { + } } diff --git a/src/main/java/com/aolda/itda/repository/certificate/CertificateRepository.java b/src/main/java/com/aolda/itda/repository/certificate/CertificateRepository.java index 392c90b0aa31f7b445067f7db1ef490a379abf5d..1ce5c1fa17c006138df409444d9476bdd8a9b3fa 100644 --- a/src/main/java/com/aolda/itda/repository/certificate/CertificateRepository.java +++ b/src/main/java/com/aolda/itda/repository/certificate/CertificateRepository.java @@ -1,7 +1,18 @@ package com.aolda.itda.repository.certificate; import com.aolda.itda.entity.certificate.Certificate; +import com.aolda.itda.entity.forwarding.Forwarding; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; +import java.util.Optional; + public interface CertificateRepository extends JpaRepository<Certificate, Long> { + + // 단건 조회 (Soft Delete 고려) + Optional<Certificate> findByCertificateIdAndIsDeleted(Long certificateId, Boolean isDeleted); + + // 프로젝트별 목록 조회 (Soft Delete 고려) + List<Certificate> findByProjectIdAndIsDeleted(String projectId, Boolean isDeleted); + } diff --git a/src/main/java/com/aolda/itda/service/certificate/CertificateService.java b/src/main/java/com/aolda/itda/service/certificate/CertificateService.java new file mode 100644 index 0000000000000000000000000000000000000000..5c0dbaf824b7e7f5f71fb7eb4ef421c3e8bd771d --- /dev/null +++ b/src/main/java/com/aolda/itda/service/certificate/CertificateService.java @@ -0,0 +1,137 @@ +package com.aolda.itda.service.certificate; + +import com.aolda.itda.dto.PageResp; +import com.aolda.itda.dto.certificate.CertificateDTO; +import com.aolda.itda.entity.certificate.Certificate; +import com.aolda.itda.exception.CustomException; +import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.repository.certificate.CertificateRepository; +import com.aolda.itda.service.AuthService; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +@Slf4j +public class CertificateService { + + private final CertificateRepository certificateRepository; + private final AuthService authService; + + /* 인증서 하나 조회 */ + public CertificateDTO getCertificate(Long certificateId, List<String> projects) { + Certificate certificate = certificateRepository + .findByCertificateIdAndIsDeleted(certificateId, false) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + // 여기서는 ErrorCode.NOT_FOUND_CERTIFICATE 등 새로운 코드로 교체 가능 + + // 프로젝트 권한 검증 + authService.validateProjectAuth(projects, certificate.getProjectId()); + + return toDTO(certificate); + } + + /* 인증서 목록 조회 */ + public PageResp<CertificateDTO> getCertificates(String projectId) { + List<CertificateDTO> list = certificateRepository + .findByProjectIdAndIsDeleted(projectId, false) + .stream() + .map(this::toDTO) + .toList(); + + return PageResp.<CertificateDTO>builder() + .contents(list) + .build(); + } + + /* 인증서 생성 */ + public CertificateDTO createCertificate(String projectId, CertificateDTO dto, List<String> projects) { + // 프로젝트 권한 검증 + authService.validateProjectAuth(projects, projectId); + + // DTO 유효성 검사 + validateDTO(dto); + + Certificate certificate = Certificate.builder() + .projectId(projectId) + .domain(dto.getDomain()) + .description(dto.getDescription()) + .isDeleted(false) + .build(); + + certificateRepository.save(certificate); + + // 생성 로직 (certbot 호출 등) 필요 시 추가 + + return toDTO(certificate); + } + + /* 인증서 수정 */ + public CertificateDTO editCertificate(Long certificateId, CertificateDTO dto, List<String> projects) { + Certificate certificate = certificateRepository + .findByCertificateIdAndIsDeleted(certificateId, false) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + + // 프로젝트 권한 검증 + authService.validateProjectAuth(projects, certificate.getProjectId()); + + // 필요한 필드만 수정 + if (dto.getDomain() != null) { + certificate.setDomain(dto.getDomain()); + } + if (dto.getDescription() != null) { + certificate.setDescription(dto.getDescription()); + } + // 기타 수정할 필드가 있다면 추가 + + // DB 저장 + certificateRepository.save(certificate); + + // 수정 로직(certbot 재발급 등) 필요 시 추가 + + return toDTO(certificate); + } + + /* 인증서 삭제 (soft delete) */ + public void deleteCertificate(Long certificateId, List<String> projects) { + Certificate certificate = certificateRepository + .findByCertificateIdAndIsDeleted(certificateId, false) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + + // 권한 검증 + authService.validateProjectAuth(projects, certificate.getProjectId()); + + // soft delete + certificate.setIsDeleted(true); + certificateRepository.save(certificate); + + // (추가) 파일 제거 / certbot revoke 등 로직 필요 시 + } + + /* DTO 유효성 검사 */ + private void validateDTO(CertificateDTO dto) { + for (ConstraintViolation<CertificateDTO> violation + : Validation.buildDefaultValidatorFactory().getValidator().validate(dto)) { + throw new CustomException(ErrorCode.INVALID_CONF_INPUT, violation.getMessage()); + } + } + + /* Entity -> DTO 변환 */ + private CertificateDTO toDTO(Certificate certificate) { + return CertificateDTO.builder() + .certificateId(certificate.getCertificateId()) + .projectId(certificate.getProjectId()) + .domain(certificate.getDomain()) + .description(certificate.getDescription()) + .isDeleted(certificate.getIsDeleted()) + .expiredAt(certificate.getExpiredAt()) + .build(); + } +}