Skip to content

Commit

Permalink
Merge pull request #42 from pyro-yolog/feat/#41-inquiry-read-delete
Browse files Browse the repository at this point in the history
Feat/#41 문의하기 조회 & 삭제 & 답변 기능 구현
  • Loading branch information
yeonjy authored Jul 1, 2024
2 parents 8c8a3fc + 3cf434e commit 9ed933d
Show file tree
Hide file tree
Showing 15 changed files with 431 additions and 18 deletions.
69 changes: 69 additions & 0 deletions src/main/java/com/pyro/yolog/domain/inquiry/api/InquiryApi.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package com.pyro.yolog.domain.inquiry.api;

import com.pyro.yolog.domain.inquiry.dto.request.InquiryAnswerRequest;
import com.pyro.yolog.domain.inquiry.dto.request.InquiryRequest;
import com.pyro.yolog.domain.inquiry.dto.response.DetailInquiryResponse;
import com.pyro.yolog.domain.inquiry.dto.response.InquiryPreview;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;

import java.util.List;

@Tag(name = "Inquiry")
public interface InquiryApi {
@Operation(
Expand All @@ -21,4 +28,66 @@ public interface InquiryApi {
)
})
void createInquiry(InquiryRequest request);

@Operation(
summary = "문의하기 전체 조회",
description = "사용자가 제출한 문의를 전체 조회 합니다.",
security = {@SecurityRequirement(name = "access_token")}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "문의하기 전체 조회에 성공했습니다."
)
})
List<InquiryPreview> getAllInquiries();

@Operation(
summary = "문의하기 상세 조회",
description = "문의 ID 값으로 문의를 상세 조회합니다.",
security = {@SecurityRequirement(name = "access_token")}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "문의하기 상세 조회에 성공했습니다."
)
})
DetailInquiryResponse getDetailInquiry(
@Parameter(in = ParameterIn.PATH, description = "문의 ID", required = true)
Long id
);

@Operation(
summary = "문의하기 답변",
description = "문의에 대하여 답변합니다.",
security = {@SecurityRequirement(name = "access_token")}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "문의하기 답변에 성공했습니다."
)
})
void updateAnswer(
@Parameter(in = ParameterIn.PATH, description = "문의 ID", required = true)
Long id,
InquiryAnswerRequest request
);

@Operation(
summary = "문의하기 삭제",
description = "문의를 삭제합니다.",
security = {@SecurityRequirement(name = "access_token")}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "204",
description = "문의 삭제하기에 성공했습니다."
)
})
void deleteAnswer(
@Parameter(in = ParameterIn.PATH, description = "문의 ID", required = true)
Long id
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package com.pyro.yolog.domain.inquiry.api;

import com.pyro.yolog.domain.inquiry.dto.request.InquiryAnswerRequest;
import com.pyro.yolog.domain.inquiry.dto.request.InquiryRequest;
import com.pyro.yolog.domain.inquiry.dto.response.DetailInquiryResponse;
import com.pyro.yolog.domain.inquiry.dto.response.InquiryPreview;
import com.pyro.yolog.domain.inquiry.service.InquiryService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("inquiries")
Expand All @@ -19,4 +24,32 @@ public class InquiryController implements InquiryApi {
public void createInquiry(@Valid @RequestBody InquiryRequest request) {
inquiryService.createInquiry(request);
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("")
@Override
public List<InquiryPreview> getAllInquiries() {
return inquiryService.getAllInquiries();
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/{id}")
@Override
public DetailInquiryResponse getDetailInquiry(@PathVariable Long id) {
return inquiryService.getDetailInquiry(id);
}

@ResponseStatus(HttpStatus.NO_CONTENT)
@PatchMapping("/{id}")
@Override
public void updateAnswer(@PathVariable Long id, @Valid @RequestBody InquiryAnswerRequest request) {
inquiryService.updateAnswer(id, request);
}

@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping("/{id}")
@Override
public void deleteAnswer(@PathVariable Long id) {
inquiryService.deleteInquiry(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.pyro.yolog.domain.inquiry.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class InquiryAnswerRequest {
@Schema(description = "답변 내용입니다. 공백으로 제출하면 안됩니다.")
@NotBlank
private String answer;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
@NoArgsConstructor
@Builder
public class InquiryRequest {
@Schema(description = "문의 제목입니다. 공백으로 제출하면 안됩니다.")
@NotBlank
private String title;

@Schema(description = "문의 내용입니다. 공백으로 제출하면 안됩니다.")
@NotBlank
private String content;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.pyro.yolog.domain.inquiry.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@AllArgsConstructor
@Builder
public class DetailInquiryResponse {
@Schema(description = "문의 ID")
private Long id;
private String title;
private String content;
@Schema(description = "문의에 대한 답변")
private String answer;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul")
private LocalDateTime createdAt;

@Schema(description = "답변 완료되었는지 여부")
private Boolean isAnswered;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.pyro.yolog.domain.inquiry.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@AllArgsConstructor
@Builder
public class InquiryPreview {
@Schema(description = "문의 ID")
private Long id;
private String title;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul")
private LocalDateTime createdAt;

@Schema(description = "답변 완료되었는지 여부")
private Boolean isAnswered;
}
15 changes: 14 additions & 1 deletion src/main/java/com/pyro/yolog/domain/inquiry/entity/Inquiry.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;

@Getter
@Entity
Expand All @@ -16,15 +17,27 @@ public class Inquiry extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String title;
private String content;
private String answer;

@Column(nullable = false)
@ColumnDefault("false")
private Boolean isAnswered;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "member_id")
private Member member;

@Builder
public Inquiry(String content, Member member) {
public Inquiry(String title, String content, Member member) {
this.title = title;
this.content = content;
this.member = member;
}

public void updateAnswer(String answer) {
this.answer = answer;
this.isAnswered = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.pyro.yolog.domain.inquiry.exception;

import com.pyro.yolog.global.error.ErrorCode;
import com.pyro.yolog.global.error.exception.BusinessException;

public class InquiryAnswerNotAdminMemberException extends BusinessException {
public InquiryAnswerNotAdminMemberException() {
super(ErrorCode.INQUIRY_NOT_ADMIN_MEMBER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.pyro.yolog.domain.inquiry.exception;

import com.pyro.yolog.global.error.ErrorCode;
import com.pyro.yolog.global.error.exception.BusinessException;

public class InquiryNotFoundException extends BusinessException {
public InquiryNotFoundException() {
super(ErrorCode.INQUIRY_NOT_FOUND_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.pyro.yolog.domain.inquiry.mapper;

import com.pyro.yolog.domain.inquiry.dto.response.DetailInquiryResponse;
import com.pyro.yolog.domain.inquiry.dto.response.InquiryPreview;
import com.pyro.yolog.domain.inquiry.entity.Inquiry;
import com.pyro.yolog.domain.member.entity.Member;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface InquiryMapper {
Inquiry toEntity(Member member, String content);
Inquiry toEntity(Member member, String title, String content);

InquiryPreview toPreviewResponse(Inquiry inquiry);

DetailInquiryResponse toDetailResponse(Inquiry inquiry);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package com.pyro.yolog.domain.inquiry.service;

import com.pyro.yolog.domain.inquiry.dto.CreateInquiryImageDto;
import com.pyro.yolog.domain.inquiry.dto.request.InquiryAnswerRequest;
import com.pyro.yolog.domain.inquiry.dto.request.InquiryRequest;
import com.pyro.yolog.domain.inquiry.dto.response.DetailInquiryResponse;
import com.pyro.yolog.domain.inquiry.dto.response.InquiryPreview;
import com.pyro.yolog.domain.inquiry.entity.Inquiry;
import com.pyro.yolog.domain.inquiry.exception.InquiryAnswerNotAdminMemberException;
import com.pyro.yolog.domain.inquiry.exception.InquiryNotFoundException;
import com.pyro.yolog.domain.inquiry.mapper.InquiryMapper;
import com.pyro.yolog.domain.inquiry.repository.InquiryRepository;
import com.pyro.yolog.domain.member.entity.Member;
import com.pyro.yolog.domain.auth.service.AuthService;
import com.pyro.yolog.domain.member.entity.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
Expand All @@ -24,8 +33,35 @@ public class InquiryService {
public void createInquiry(InquiryRequest request) {
Member member = authService.getLoginUser();
Inquiry inquiry = inquiryRepository.save(inquiryMapper
.toEntity(member, request.getContent()));
.toEntity(member, request.getTitle(), request.getContent()));
inquiryImageService.saveImages(
new CreateInquiryImageDto(inquiry, request.getImageUrls()));
}

@Transactional(readOnly = true)
public List<InquiryPreview> getAllInquiries() {
Member member = authService.getLoginUser();
return inquiryRepository.findAllByMember(member).stream()
.map(inquiryMapper::toPreviewResponse).collect(Collectors.toList());
}

public DetailInquiryResponse getDetailInquiry(Long id) {
Inquiry inquiry = inquiryRepository.findById(id).orElseThrow(InquiryNotFoundException::new);
return inquiryMapper.toDetailResponse(inquiry);
}

@Transactional
public void updateAnswer(Long id, InquiryAnswerRequest request) {
Member member = authService.getLoginUser();
if (!member.getRole().equals(Role.ADMIN)) {
throw new InquiryAnswerNotAdminMemberException();
}
Inquiry inquiry = inquiryRepository.findById(id).orElseThrow(InquiryNotFoundException::new);
inquiry.updateAnswer(request.getAnswer());
}

@Transactional
public void deleteInquiry(Long id) {
inquiryRepository.deleteById(id);
}
}
7 changes: 6 additions & 1 deletion src/main/java/com/pyro/yolog/global/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ public enum ErrorCode {
// S3
FILE_UPLOAD_FAILURE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S3 파일 업로드에 실패했습니다."),
INVALID_FILE_EXTENSION_ERROR(HttpStatus.BAD_REQUEST, "잘못된 확장자의 파일 업로드를 시도했습니다."),
FILE_DELETE_FAILURE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S3 파일 삭제에 실패했습니다.");
FILE_DELETE_FAILURE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S3 파일 삭제에 실패했습니다."),

// INQUIRY
INQUIRY_NOT_FOUND_ERROR(HttpStatus.NOT_FOUND, "해당 문의를 찾지 못했습니다."),
INQUIRY_NOT_ADMIN_MEMBER(HttpStatus.BAD_REQUEST, "문의하기 답변은 관리자만 가능합니다."),
;

private final HttpStatus status;
private final String errorMessage;
Expand Down
Loading

0 comments on commit 9ed933d

Please sign in to comment.