Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 공지사항 및 답변 API를 구현하고, 테스트 코드를 작성한다 #57

Merged
merged 4 commits into from
Oct 12, 2023
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,37 @@
package com.example.cargive.domain.answer.controller;

import com.example.cargive.domain.answer.controller.dto.AnswerRequest;
import com.example.cargive.domain.answer.service.AnswerService;
import com.example.cargive.global.base.BaseResponse;
import com.example.cargive.global.base.BaseResponseStatus;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/answer")
public class AnswerController {
private final AnswerService answerService;

@PostMapping("/{questionId}")
public ResponseEntity<BaseResponse> createAnswer(@PathVariable Long questionId,
@RequestBody @Valid AnswerRequest request) {
answerService.createAnswer(request, questionId);
return BaseResponse.toResponseEntityContainsStatus(BaseResponseStatus.CREATED);
}

@PutMapping("/{answerId}")
public ResponseEntity<BaseResponse> editAnswer(@PathVariable Long answerId,
@RequestBody @Valid AnswerRequest request) {
answerService.editAnswer(request, answerId);
return BaseResponse.toResponseEntityContainsStatus(BaseResponseStatus.SUCCESS);
}

@DeleteMapping("/{answerId}")
public ResponseEntity<BaseResponse> deleteAnswer(@PathVariable Long answerId) {
answerService.deleteAnswer(answerId);
return BaseResponse.toResponseEntityContainsStatus(BaseResponseStatus.DELETED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.cargive.domain.answer.controller.dto;

import jakarta.validation.constraints.NotBlank;

public record AnswerRequest(
@NotBlank(message = "답변 내용을 입력해주세요")
String content
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ public Answer(String content, Question question) {
this.status = Status.NORMAL;
this.question = question;
}

public void editInfo(String content) {
this.content = content;
}

public void deleteEntity() {
this.status = Status.EXPIRED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.cargive.domain.answer.entity.repository;

import com.example.cargive.domain.answer.entity.Answer;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AnswerRepository extends JpaRepository<Answer, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.cargive.domain.answer.service;

import com.example.cargive.domain.answer.entity.Answer;
import com.example.cargive.domain.answer.controller.dto.AnswerRequest;
import com.example.cargive.domain.answer.entity.repository.AnswerRepository;
import com.example.cargive.domain.question.entity.Question;
import com.example.cargive.domain.question.entity.repository.QuestionRepository;
import com.example.cargive.global.base.BaseException;
import com.example.cargive.global.base.BaseResponseStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class AnswerService {
private final AnswerRepository answerRepository;
private final QuestionRepository questionRepository;

public void createAnswer(AnswerRequest request, Long questionId) {
Question findQuestion = getQuestion(questionId);
Answer answer = createAnswer(request, findQuestion);

answerRepository.save(answer);
}

public void editAnswer(AnswerRequest request, Long answerId) {
Answer findAnswer = getAnswer(answerId);
findAnswer.editInfo(request.content());
}

public void deleteAnswer(Long answerId) {
Answer findAnswer = getAnswer(answerId);
findAnswer.deleteEntity();
}

private Answer createAnswer(AnswerRequest request, Question question) {
return new Answer(request.content(), question);
}

private Question getQuestion(Long questionId) {
return questionRepository.findById(questionId)
.orElseThrow(() -> new BaseException(BaseResponseStatus.QUESTION_NOT_FOUND_ERROR));
}

private Answer getAnswer(Long answerId) {
return answerRepository.findById(answerId)
.orElseThrow(() -> new BaseException(BaseResponseStatus.ANSWER_NOT_FOUND_ERROR));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.example.cargive.domain.notice.controller;

import com.example.cargive.domain.notice.controller.dto.request.NoticeRequest;
import com.example.cargive.domain.notice.service.NoticeService;
import com.example.cargive.global.base.BaseResponse;
import com.example.cargive.global.base.BaseResponseStatus;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/notice")
public class NoticeController {
private final NoticeService noticeService;

@GetMapping
public ResponseEntity<BaseResponse> getNoticeList() {
return BaseResponse.toResponseEntityContainsResult(noticeService.getNoticeList());
}

@GetMapping("/{noticeId}")
public ResponseEntity<BaseResponse> getNoticeInfo(@PathVariable Long noticeId) {
return BaseResponse.toResponseEntityContainsResult(noticeService.getNoticeInfo(noticeId));
}

@PostMapping
public ResponseEntity<BaseResponse> createNotice(@RequestBody @Valid NoticeRequest request) {
noticeService.createNotice(request);
return BaseResponse.toResponseEntityContainsStatus(BaseResponseStatus.CREATED);
}

@PutMapping("/{noticeId}")
public ResponseEntity<BaseResponse> editNotice(@RequestBody @Valid NoticeRequest request,
@PathVariable Long noticeId) {
noticeService.editNotice(request, noticeId);
return BaseResponse.toResponseEntityContainsResult(BaseResponseStatus.SUCCESS);
}

@DeleteMapping("/{noticeId}")
public ResponseEntity<BaseResponse> deleteNotice(@PathVariable Long noticeId) {
noticeService.deleteNotice(noticeId);
return BaseResponse.toResponseEntityContainsStatus(BaseResponseStatus.DELETED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.cargive.domain.notice.controller.dto.request;

import jakarta.validation.constraints.NotBlank;

public record NoticeRequest(
@NotBlank(message = "공지 사항의 제목을 입력해주세요")
String title,
@NotBlank(message = "공지 사항의 내용을 입력해주세요")
String content
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.cargive.domain.notice.controller.dto.response;

import com.example.cargive.domain.notice.entity.Notice;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class NoticeResponse {
private String title;
private String content;
private LocalDateTime createAt;

public static NoticeResponse toDto(Notice notice) {
return new NoticeResponse(notice.getTitle(), notice.getContent(), notice.getCreateAt());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.example.cargive.domain.notice.entity;

import com.example.cargive.global.domain.BaseEntity;
import com.example.cargive.global.template.Status;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Table(name = "notice")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Notice extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false)
private String content;

@Convert(converter = Status.StatusConverter.class)
private Status status;

public Notice(String title, String content) {
this.title = title;
this.content = content;
this.status = Status.NORMAL;
}

public void editInfo(String title, String content) {
this.title = title;
this.content = content;
}

public void deleteEntity() {
this.status = Status.EXPIRED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.cargive.domain.notice.entity.repository;

import com.example.cargive.domain.notice.entity.Notice;
import com.example.cargive.domain.notice.infra.NoticeQueryRepository;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NoticeRepository extends JpaRepository<Notice, Long>, NoticeQueryRepository {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.cargive.domain.notice.infra;

import com.example.cargive.domain.notice.infra.dto.NoticeQueryResponse;

import java.util.List;

public interface NoticeQueryRepository {
List<NoticeQueryResponse> getNoticeResponseList();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.cargive.domain.notice.infra;

import com.example.cargive.domain.notice.infra.dto.NoticeQueryResponse;
import com.example.cargive.domain.notice.infra.dto.QNoticeQueryResponse;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static com.example.cargive.domain.notice.entity.QNotice.*;

@RequiredArgsConstructor
@Transactional(readOnly = true)
public class NoticeQueryRepositoryImpl implements NoticeQueryRepository {
private final JPAQueryFactory jpaQueryFactory;
private final int PAGE_SIZE = 6;

@Override
public List<NoticeQueryResponse> getNoticeResponseList() {
return jpaQueryFactory
.selectDistinct(new QNoticeQueryResponse(notice.title, notice.createAt))
.from(notice)
.orderBy(notice.createAt.desc())
.limit(PAGE_SIZE)
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.cargive.domain.notice.infra.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
public class NoticeQueryResponse {
private String title;
private LocalDateTime createAt;

@QueryProjection
public NoticeQueryResponse(String title, LocalDateTime createAt) {
this.title = title;
this.createAt = createAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.example.cargive.domain.notice.service;

import com.example.cargive.domain.notice.controller.dto.request.NoticeRequest;
import com.example.cargive.domain.notice.controller.dto.response.NoticeResponse;
import com.example.cargive.domain.notice.entity.Notice;
import com.example.cargive.domain.notice.entity.repository.NoticeRepository;
import com.example.cargive.domain.notice.infra.dto.NoticeQueryResponse;
import com.example.cargive.global.base.BaseException;
import com.example.cargive.global.base.BaseResponseStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class NoticeService {
private final NoticeRepository noticeRepository;

@Transactional(readOnly = true)
public List<NoticeQueryResponse> getNoticeList() {
return getNoticeResponseList();
}

@Transactional(readOnly = true)
public NoticeResponse getNoticeInfo(Long noticeId) {
Notice findNotice = getNotice(noticeId);

return NoticeResponse.toDto(findNotice);
}

@Transactional
public void createNotice(NoticeRequest request) {
Notice notice = createNoticeEntity(request);

noticeRepository.save(notice);
}

@Transactional
public void editNotice(NoticeRequest request, Long noticeId) {
Notice findNotice = getNotice(noticeId);

findNotice.editInfo(request.title(), request.content());
}

@Transactional
public void deleteNotice(Long noticeId) {
Notice findNotice = getNotice(noticeId);

findNotice.deleteEntity();
}

private List<NoticeQueryResponse> getNoticeResponseList() {
List<NoticeQueryResponse> responseList = noticeRepository.getNoticeResponseList();

if(responseList.isEmpty()) throw new BaseException(BaseResponseStatus.NOTICE_LIST_EMPTY_ERROR);

return responseList;
}

private Notice createNoticeEntity(NoticeRequest request) {
return new Notice(request.title(), request.content());
}

private Notice getNotice(Long noticeId) {
return noticeRepository.findById(noticeId)
.orElseThrow(() -> new BaseException(BaseResponseStatus.NOTICE_NOT_FOUND_ERROR));
}
}
Loading
Loading