Skip to content

Commit

Permalink
✨ feat: 민원 CR API 구현(#62)
Browse files Browse the repository at this point in the history
✨ feat: 민원 CR API 구현(#62)
  • Loading branch information
2hy2on authored Aug 15, 2024
2 parents d2c3973 + 4062e93 commit aa7c5f0
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.example.template.domain.board.entity.BoardImg;
import com.example.template.domain.board.entity.CommentImg;
import com.example.template.domain.board.repository.BoardImgRepository;
import com.example.template.domain.complaint.entity.ComplaintImg;
import com.example.template.domain.complaint.repository.ComplaintImgRepository;
import com.example.template.domain.board.repository.CommentImgRepository;
import com.example.template.domain.message.entity.MessageImg;
import com.example.template.domain.message.repository.MessageImgRepository;
Expand All @@ -23,6 +25,7 @@ public class UnmappedImageCleanupScheduler {
private final BoardImgRepository boardImgRepository;
private final CommentImgRepository commentImgRepository;
private final MessageImgRepository messageImgRepository;
private final ComplaintImgRepository complaintImgRepository;
private final S3Manager s3Manager;

@Scheduled(cron = "0 0 3 * * ?") // 매일 새벽 3시에 실행
Expand Down Expand Up @@ -59,6 +62,21 @@ public void cleanupUnmappedMessageImages() {
log.info("[cleanupUnmappedMessageImages 실행] 쪽지 이미지 삭제완료");
}


@Scheduled(cron = "0 0 4 * * ?") // 매일 새벽 4시에 실행
@Transactional
public void cleanupUnmappedComplaintImages() {
List<ComplaintImg> unmappedImages = complaintImgRepository.findUnmappedImages();

for (ComplaintImg image : unmappedImages) {
s3Manager.deleteFile(image.getImgUrl());
complaintImgRepository.delete(image);
}

log.info("[cleanupUnmappedComplaintImages 실행] 민원 이미지 삭제완료");
}


// TODO: 테스트용 수동 트리거 메서드 - 삭제 예정
public void manualCleanup() {
this.cleanupUnmappedImages();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.example.template.domain.complaint.controller;

import com.example.template.domain.complaint.dto.request.ComplaintRequestDTO;
import com.example.template.domain.complaint.dto.response.ComplaintResponseDTO;
import com.example.template.domain.complaint.service.ComplaintCommandService;
import com.example.template.domain.complaint.service.ComplaintQueryService;
import com.example.template.domain.member.entity.Member;
import com.example.template.global.annotation.AuthenticatedMember;
import com.example.template.global.apiPayload.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/complaints")
public class ComplaintController {

private final ComplaintQueryService complaintQueryService;
private final ComplaintCommandService complaintCommandService;

@Operation(summary = "민원글 쓰기 전 충전소 이름 얻어오기",description = "민원글 쓰기 누를 때 호출")
@GetMapping("/station/{stationId}")
ApiResponse<ComplaintResponseDTO.getStationDTO> getStationName(@PathVariable("stationId") Long stationId){
return ApiResponse.onSuccess(complaintQueryService.getStationName(stationId));
}

@Operation(summary = "이미지 업로드", description = "민원글에 첨부할 이미지를 업로드.")
@PostMapping(value = "/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<ComplaintResponseDTO.ComplaintImgDTO> uploadImages(
@RequestPart("images") List<MultipartFile> images) {
return ApiResponse.onSuccess(complaintCommandService.uploadImages(images));
}

@Operation(summary = "새 민원글 작성")
@PostMapping()
public ApiResponse<ComplaintResponseDTO.ComplaintDTO> createComplaint(@AuthenticatedMember Member member,
@Valid @RequestBody ComplaintRequestDTO.CreateComplaintDTO createComplaintDTO) {
return ApiResponse.onSuccess(complaintCommandService.createComplaint(member, createComplaintDTO));
}


@Operation(summary = "내가 쓴 민원글 상세 조회")
@GetMapping("/{complaintId}")
ApiResponse<ComplaintResponseDTO.ComplaintDTO>getComplaintDetail(@AuthenticatedMember Member member, @PathVariable("complaintId") Long complaintId){
return ApiResponse.onSuccess(complaintQueryService.getComplaintDetail(member, complaintId));
}

@Operation(summary = "내가 쓴 민원글 목록 조회")
@GetMapping()
ApiResponse<List<ComplaintResponseDTO.ComplaintDTO>> getComplaintList(@AuthenticatedMember Member member){
return ApiResponse.onSuccess(complaintQueryService.getComplaintList(member));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.template.domain.complaint.dto.request;

import com.example.template.domain.complaint.entity.Complaint;
import com.example.template.domain.complaint.entity.ComplaintType;
import com.example.template.domain.member.entity.Member;
import com.example.template.domain.station.entity.Station;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;

import java.util.List;

public class ComplaintRequestDTO {
@Getter
@Builder
public static class CreateComplaintDTO {

private Long stationId;
@NotNull(message = "제목은 필수입니다.")
private String title;
@NotNull(message = "내용은 필수입니다.")
private String content;
@NotNull(message = "카테고리는 필수입니다.")
private ComplaintType complaintType;

private List<String> images;

public static Complaint toEntity(CreateComplaintDTO createComplaintDTO, Station station, Member member) {
return Complaint.builder()
.title(createComplaintDTO.getTitle())
.complaintType(createComplaintDTO.getComplaintType())
.content(createComplaintDTO.getContent())
.member(member)
.station(station).build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.example.template.domain.complaint.dto.response;

import com.example.template.domain.complaint.entity.Complaint;
import com.example.template.domain.complaint.entity.ComplaintImg;
import com.example.template.domain.complaint.entity.ComplaintType;
import com.example.template.domain.station.entity.Station;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@RequiredArgsConstructor
public class ComplaintResponseDTO {

@Builder
@Getter
public static class getStationDTO{
private String stationName;

public static getStationDTO from(Station station) {
return getStationDTO.builder()
.stationName(station.getName()).build();
}
}

@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@Getter
public static class ComplaintDTO {
private Long complaintId;
private String title;
private String content;
private ComplaintType complaintType;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private List<String> images;

public static ComplaintDTO from(Complaint complaint, List<ComplaintImg> complaintImgList){
List<String> complaintImgURL = complaintImgList.stream()
.map(ComplaintImg::getImgUrl)
.collect(Collectors.toList());

return ComplaintDTO.builder()
.complaintId(complaint.getId())
.complaintType(complaint.getComplaintType())
.title(complaint.getTitle())
.content(complaint.getContent())
.images(complaintImgURL)
.createdAt(complaint.getCreatedAt())
.updatedAt(complaint.getUpdatedAt())
.build();
}

//목록조회를 위해 이미지가 없는 버전
public static ComplaintDTO from(Complaint complaint){
return ComplaintDTO.builder()
.complaintId(complaint.getId())
.complaintType(complaint.getComplaintType())
.title(complaint.getTitle())
.content(complaint.getContent())
.createdAt(complaint.getCreatedAt())
.updatedAt(complaint.getUpdatedAt())
.build();
}

public static List<ComplaintDTO> from(List<Complaint> complaints) {
return complaints.stream()
.map(ComplaintDTO::from)
.collect(Collectors.toList());
}
}

@Getter
@Builder
public static class ComplaintImgDTO {
private List<String> images;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public class Complaint extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "station_id")
private Station station;

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ public class ComplaintImg {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "complaint_id")
private Complaint complaint;

public void updateComplaint(Complaint complaint) {
this.complaint = complaint;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.template.domain.complaint.exception;

import com.example.template.global.apiPayload.ApiResponse;
import com.example.template.global.apiPayload.code.status.BaseErrorCode;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum ComplaintErrorCode implements BaseErrorCode {

// BOARD 에러
COMPLAINT_NOT_FOUND(HttpStatus.NOT_FOUND, "COMPLAINT404", "민원글을 찾을 수 없습니다."),

//IMAGE 에러
INVALID_IMAGE_URLS(HttpStatus.BAD_REQUEST, "COMPLAINT400", "일부 이미지 URL이 유효하지 않거나 찾을 수 없습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;

@Override
public ApiResponse<Void> getErrorResponse() {
return ApiResponse.onFailure(code, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.template.domain.complaint.exception;

import com.example.template.global.apiPayload.code.status.BaseErrorCode;
import com.example.template.global.apiPayload.exception.GeneralException;
import lombok.Getter;

@Getter
public class ComplaintException extends GeneralException {
public ComplaintException(BaseErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.template.domain.complaint.repository;

import com.example.template.domain.complaint.entity.ComplaintImg;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
public interface ComplaintImgRepository extends JpaRepository<ComplaintImg, Long> {

@Query("SELECT c FROM ComplaintImg c WHERE c.complaint.id = :complaintId")
List<ComplaintImg> findAllByComplaintId(@Param("complaintId")Long complaintId);


List<ComplaintImg> findAllByImgUrlIn(List<String> images);

@Query("SELECT c FROM ComplaintImg c WHERE c.complaint IS NULL")
List<ComplaintImg> findUnmappedImages();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.template.domain.complaint.repository;

import com.example.template.domain.complaint.entity.Complaint;
import com.example.template.domain.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long> {

@Query("SELECT c FROM Complaint c WHERE c.member.id = :memberId")
List<Complaint> findAllByMemberId(@Param("memberId")Long memberId);

Complaint findByIdAndMember(Long complaintId, Member member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.template.domain.complaint.service;

import com.example.template.domain.complaint.dto.request.ComplaintRequestDTO;
import com.example.template.domain.complaint.dto.response.ComplaintResponseDTO;
import com.example.template.domain.member.entity.Member;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

public interface ComplaintCommandService {
ComplaintResponseDTO.ComplaintImgDTO uploadImages(List<MultipartFile> images);

ComplaintResponseDTO.ComplaintDTO createComplaint(Member member, ComplaintRequestDTO.CreateComplaintDTO createComplaintDTO);
}
Loading

0 comments on commit aa7c5f0

Please sign in to comment.