Skip to content

Commit

Permalink
Merge pull request #88 from cvs-go/feature#85
Browse files Browse the repository at this point in the history
공지사항 조회 기능 추가
  • Loading branch information
feel-coding authored Aug 19, 2023
2 parents 8a781e7 + b28da88 commit d0b54c8
Show file tree
Hide file tree
Showing 15 changed files with 554 additions and 0 deletions.
3 changes: 3 additions & 0 deletions sql/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ insert into manufacturer (name, created_at, modified_at) values('삼각', now(),
insert into manufacturer (name, created_at, modified_at) values('', now(), now());
insert into manufacturer (name, created_at, modified_at) values('', now(), now());

-- notice
insert into notice (title, content, created_at, modified_at) values('편해 출시', '편의점 리뷰 서비스 편해가 출시되었습니다.', now(), now());

-- product
insert into product (name, price, version, category_id, manufacturer_id, created_at, modified_at) values ('진라면순한맛', 950, 0, 2, 1, now(), now());
insert into product (name, price, version, category_id, manufacturer_id, created_at, modified_at) values ('마이쮸포도', 800, 0, 3, 2, now(), now());
Expand Down
18 changes: 18 additions & 0 deletions sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ create table manufacturer (
primary key (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

create table notice (
id bigint not null auto_increment,
title varchar(255) not null,
content varchar(255) not null,
created_at datetime,
modified_at datetime,
primary key (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

create table notice_image (
id bigint not null auto_increment,
image_url varchar(255) not null,
notice_id bigint not null,
created_at datetime,
modified_at datetime,
primary key (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

create table product (
id bigint not null auto_increment,
created_at datetime,
Expand Down
25 changes: 25 additions & 0 deletions src/docs/asciidoc/api-doc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,28 @@ include::{snippets}/image-controller-test/respond_201_when_succeed_to_upload_ima
include::{snippets}/image-controller-test/respond_201_when_succeed_to_upload_images/http-request.adoc[]
==== Sample Response
include::{snippets}/image-controller-test/respond_201_when_succeed_to_upload_images/http-response.adoc[]

== 6. 공지사항
=== 6-1. 공지사항 목록 조회
==== Sample Request
include::{snippets}/notice-controller-test/respond_200_when_read_notice_list_successfully/http-request.adoc[]
==== Response Fields
include::{snippets}/notice-controller-test/respond_200_when_read_notice_list_successfully/response-fields.adoc[]
==== Sample Response
include::{snippets}/notice-controller-test/respond_200_when_read_notice_list_successfully/http-response.adoc[]

=== 6-2. 공지사항 상세 조회
==== Path Parameters
include::{snippets}/notice-controller-test/respond_200_when_read_notice_successfully/path-parameters.adoc[]
==== Sample Request
include::{snippets}/notice-controller-test/respond_200_when_read_notice_successfully/http-request.adoc[]
==== Response Fields
include::{snippets}/notice-controller-test/respond_200_when_read_notice_successfully/response-fields.adoc[]
==== Sample Response
include::{snippets}/notice-controller-test/respond_200_when_read_notice_successfully/http-response.adoc[]
==== Error Response
|===
| HTTP Status | Error Code | Detail

| `404 NOT FOUND` | `NOT_FOUND_NOTICE` | 해당하는 공지사항이 없는 경우
|===
33 changes: 33 additions & 0 deletions src/main/java/com/cvsgo/controller/NoticeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.cvsgo.controller;

import com.cvsgo.dto.SuccessResponse;
import com.cvsgo.dto.notice.ReadNoticeDetailResponseDto;
import com.cvsgo.dto.notice.ReadNoticeResponseDto;
import com.cvsgo.service.NoticeService;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/notices")
public class NoticeController {

private final NoticeService noticeService;

@GetMapping
public SuccessResponse<Page<ReadNoticeResponseDto>> readNoticeList(Pageable pageable) {
return SuccessResponse.from(noticeService.readNoticeList(pageable));
}

@GetMapping("/{noticeId}")
public SuccessResponse<ReadNoticeDetailResponseDto> readNotice(@PathVariable Long noticeId) {
return SuccessResponse.from(noticeService.readNotice(noticeId));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.cvsgo.dto.notice;

import com.cvsgo.entity.Notice;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Getter;

@Getter
public class ReadNoticeDetailResponseDto {

private final Long id;

private final String title;

private final String content;

private final List<String> noticeImageUrls;

@JsonFormat(pattern = "yy.MM.dd", timezone = "Asia/Seoul")
private final LocalDateTime createdAt;

public ReadNoticeDetailResponseDto(Notice notice, List<String> noticeImageUrls) {
this.id = notice.getId();
this.title = notice.getTitle();
this.content = notice.getContent();
this.noticeImageUrls = noticeImageUrls;
this.createdAt = notice.getCreatedAt();
}

public static ReadNoticeDetailResponseDto of(Notice notice, List<String> noticeImageUrls) {
return new ReadNoticeDetailResponseDto(notice, noticeImageUrls);
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/cvsgo/dto/notice/ReadNoticeResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.cvsgo.dto.notice;

import com.cvsgo.entity.Notice;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import lombok.Getter;

@Getter
public class ReadNoticeResponseDto {

private final Long id;

private final String title;

@JsonFormat(pattern = "yy.MM.dd", timezone = "Asia/Seoul")
private final LocalDateTime createdAt;

private final boolean isNew;

public ReadNoticeResponseDto(Notice notice) {
this.id = notice.getId();
this.title = notice.getTitle();
this.createdAt = notice.getCreatedAt();
this.isNew = notice.getCreatedAt() != null
&& ChronoUnit.DAYS.between(notice.getCreatedAt().toLocalDate(), LocalDate.now()) <= 7;
}

public static ReadNoticeResponseDto from(Notice notice) {
return new ReadNoticeResponseDto(notice);
}

}
54 changes: 54 additions & 0 deletions src/main/java/com/cvsgo/entity/Notice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.cvsgo.entity;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Notice extends BaseTimeEntity {

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

@NotNull
private String title;

@NotNull
private String content;

@OneToMany(mappedBy = "notice", cascade = CascadeType.ALL, orphanRemoval = true)
private final List<NoticeImage> noticeImages = new ArrayList<>();

public void addImage(String imageUrl) {
NoticeImage noticeImage = NoticeImage.builder()
.notice(this)
.imageUrl(imageUrl)
.build();
noticeImages.add(noticeImage);
}

@Builder
public Notice(Long id, String title, String content, List<String> imageUrls) {
this.id = id;
this.title = title;
this.content = content;
if (imageUrls != null) {
for (String imageUrl : imageUrls) {
addImage(imageUrl);
}
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/cvsgo/entity/NoticeImage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.cvsgo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

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

private String imageUrl;

@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "notice_id")
private Notice notice;

@Builder
public NoticeImage(Long id, String imageUrl, Notice notice) {
this.id = id;
this.imageUrl = imageUrl;
this.notice = notice;
}
}
1 change: 1 addition & 0 deletions src/main/java/com/cvsgo/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public enum ErrorCode {
NOT_FOUND_PRODUCT_BOOKMARK("존재하지 않는 상품 북마크입니다."),
NOT_FOUND_REVIEW("존재하지 않는 리뷰입니다."),
NOT_FOUND_REVIEW_LIKE("해당 리뷰의 좋아요가 없습니다."),
NOT_FOUND_NOTICE("존재하지 않는 공지사항입니다."),

/* 500 INTERNAL_SEVER_ERROR */
UNEXPECTED_ERROR("시스템에 문제가 발생했습니다.");
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/cvsgo/exception/ExceptionConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface ExceptionConstants {
NotFoundException NOT_FOUND_PRODUCT_BOOKMARK = new NotFoundException(ErrorCode.NOT_FOUND_PRODUCT_BOOKMARK);
NotFoundException NOT_FOUND_REVIEW = new NotFoundException(ErrorCode.NOT_FOUND_REVIEW);
NotFoundException NOT_FOUND_REVIEW_LIKE = new NotFoundException(ErrorCode.NOT_FOUND_REVIEW_LIKE);
NotFoundException NOT_FOUND_NOTICE = new NotFoundException(ErrorCode.NOT_FOUND_NOTICE);

DuplicateException DUPLICATE_EMAIL = new DuplicateException(ErrorCode.DUPLICATE_EMAIL);
DuplicateException DUPLICATE_NICKNAME = new DuplicateException(ErrorCode.DUPLICATE_NICKNAME);
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/cvsgo/repository/NoticeImageRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.cvsgo.repository;

import com.cvsgo.entity.Notice;
import com.cvsgo.entity.NoticeImage;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NoticeImageRepository extends JpaRepository<NoticeImage, Long> {

List<NoticeImage> findByNotice(Notice notice);
}
12 changes: 12 additions & 0 deletions src/main/java/com/cvsgo/repository/NoticeRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.cvsgo.repository;

import com.cvsgo.entity.Notice;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NoticeRepository extends JpaRepository<Notice, Long> {

Page<Notice> findAllByOrderByCreatedAtDesc(Pageable pageable);

}
54 changes: 54 additions & 0 deletions src/main/java/com/cvsgo/service/NoticeService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.cvsgo.service;

import static com.cvsgo.exception.ExceptionConstants.NOT_FOUND_NOTICE;

import com.cvsgo.dto.notice.ReadNoticeDetailResponseDto;
import com.cvsgo.dto.notice.ReadNoticeResponseDto;
import com.cvsgo.entity.Notice;
import com.cvsgo.entity.NoticeImage;
import com.cvsgo.exception.NotFoundException;
import com.cvsgo.repository.NoticeImageRepository;
import com.cvsgo.repository.NoticeRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class NoticeService {

private final NoticeRepository noticeRepository;
private final NoticeImageRepository noticeImageRepository;

/**
* 공지사항 목록을 조회한다.
*
* @return 공지사항 목록
*/
@Transactional(readOnly = true)
public Page<ReadNoticeResponseDto> readNoticeList(Pageable pageable) {
return noticeRepository.findAllByOrderByCreatedAtDesc(pageable)
.map(ReadNoticeResponseDto::from);
}

/**
* 공지사항을 상세 조회한다.
*
* @param noticeId 공지사항 ID
* @return 공지사항 상세 정보
* @throws NotFoundException 해당하는 아이디를 가진 공지사항이 없는 경우
*/
@Transactional(readOnly = true)
public ReadNoticeDetailResponseDto readNotice(Long noticeId) {
Notice notice = noticeRepository.findById(noticeId).orElseThrow(() -> NOT_FOUND_NOTICE);
List<String> noticeImages = noticeImageRepository.findByNotice(notice).stream()
.map(NoticeImage::getImageUrl).toList();

return ReadNoticeDetailResponseDto.of(notice, noticeImages);
}

}
Loading

0 comments on commit d0b54c8

Please sign in to comment.