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 추가 #576 #578

Merged
merged 12 commits into from
Jul 28, 2024
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.idea

backend/data
backend/data

.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package site.timecapsulearchive.core.domain.announcement.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.ResponseEntity;
import site.timecapsulearchive.core.domain.announcement.data.response.AnnouncementsResponse;
import site.timecapsulearchive.core.global.common.response.ApiSpec;

public interface AnnouncementApi {
@Operation(
summary = "공지사항 페이지",
description = "모든 공지사항을 가져온다. 최신 공지가 리스트의 맨 처음에 위치한다.",
tags = {"announcement"}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "ok"
)
})
ResponseEntity<ApiSpec<AnnouncementsResponse>> getAnnouncements();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package site.timecapsulearchive.core.domain.announcement.api;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import site.timecapsulearchive.core.domain.announcement.data.response.AnnouncementsResponse;
import site.timecapsulearchive.core.domain.announcement.data.dto.AnnouncementDto;
import site.timecapsulearchive.core.domain.announcement.service.AnnouncementService;
import site.timecapsulearchive.core.global.common.response.ApiSpec;
import site.timecapsulearchive.core.global.common.response.SuccessCode;

@RestController
@RequestMapping("/announcement")
@RequiredArgsConstructor
public class AnnouncementApiController implements AnnouncementApi {

private final AnnouncementService announcementService;

@GetMapping
@Override
public ResponseEntity<ApiSpec<AnnouncementsResponse>> getAnnouncements() {
List<AnnouncementDto> announcements = announcementService.findAll();

return ResponseEntity.ok(
ApiSpec.success(
SuccessCode.SUCCESS,
AnnouncementsResponse.createOf(announcements)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package site.timecapsulearchive.core.domain.announcement.data.dto;

import java.time.ZonedDateTime;
import site.timecapsulearchive.core.domain.announcement.data.response.AnnouncementResponse;

public record AnnouncementDto(
String title,
String content,
String version,
ZonedDateTime createdAt
) {

public AnnouncementResponse toResponse() {
return new AnnouncementResponse(title, content, version, createdAt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package site.timecapsulearchive.core.domain.announcement.data.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime;
import site.timecapsulearchive.core.global.common.response.ResponseMappingConstant;

@Schema(description = "공지사항 응답")
public record AnnouncementResponse(
@Schema(description = "제목")
String title,

@Schema(description = "내용")
String content,

@Schema(description = "공지사항 버전")
String version,

@Schema(description = "공지사항 생성일")
ZonedDateTime createdAt
) {

public AnnouncementResponse {
if (createdAt != null) {
createdAt = createdAt.withZoneSameInstant(ResponseMappingConstant.ZONE_ID);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package site.timecapsulearchive.core.domain.announcement.data.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import site.timecapsulearchive.core.domain.announcement.data.dto.AnnouncementDto;

@Schema(description = "공지사항 목록 응답")
public record AnnouncementsResponse(
@Schema(description = "공지사항 목록")
List<AnnouncementResponse> announcements
) {

public static AnnouncementsResponse createOf(List<AnnouncementDto> announcementDtos) {
return new AnnouncementsResponse(
announcementDtos.stream()
.map(AnnouncementDto::toResponse)
.toList()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package site.timecapsulearchive.core.domain.announcement.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import site.timecapsulearchive.core.global.entity.BaseEntity;

@Entity
@Table(name = "announcement")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Announcement extends BaseEntity {

@Id
@Column(name = "announcement_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "title")
private String title;

@Column(name = "content")
private String content;

@Column(name = "version")
private String version;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package site.timecapsulearchive.core.domain.announcement.repository;

import static site.timecapsulearchive.core.domain.announcement.entity.QAnnouncement.announcement;

import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import site.timecapsulearchive.core.domain.announcement.data.dto.AnnouncementDto;

@Repository
@RequiredArgsConstructor
public class AnnouncementRepository {

private final JPAQueryFactory jpaQueryFactory;

public List<AnnouncementDto> findAll() {
return jpaQueryFactory
.select(
Projections.constructor(
AnnouncementDto.class,
announcement.title,
announcement.content,
announcement.version,
announcement.createdAt
)
)
.from(announcement)
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package site.timecapsulearchive.core.domain.announcement.service;

import java.util.Comparator;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import site.timecapsulearchive.core.domain.announcement.data.dto.AnnouncementDto;
import site.timecapsulearchive.core.domain.announcement.repository.AnnouncementRepository;

@Service
@RequiredArgsConstructor
public class AnnouncementService {

private final AnnouncementRepository announcementRepository;

public List<AnnouncementDto> findAll() {
List<AnnouncementDto> announcements = announcementRepository.findAll();

announcements.sort(Comparator.comparing(AnnouncementDto::createdAt).reversed());
seokho-1116 marked this conversation as resolved.
Show resolved Hide resolved

return announcements;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
create table announcement (
announcement_id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
content TEXT,
version VARCHAR(30),
created_at timestamp NULL,
updated_at timestamp NULL
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package site.timecapsulearchive.core.common.fixture.domain;

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import site.timecapsulearchive.core.domain.announcement.data.dto.AnnouncementDto;

public class AnnouncementTestFixture {

public static List<AnnouncementDto> announcementDtos(int size) {
List<AnnouncementDto> result = new ArrayList<>();
ZonedDateTime now = ZonedDateTime.now();
for (int index = 0; index < size; index++) {
result.add(
new AnnouncementDto("title" + index, "content" + index, String.valueOf(index),
now.plusSeconds(index))
);
}

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package site.timecapsulearchive.core.domain.announcement.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

import java.util.Comparator;
import java.util.List;
import org.junit.jupiter.api.Test;
import site.timecapsulearchive.core.common.fixture.domain.AnnouncementTestFixture;
import site.timecapsulearchive.core.domain.announcement.data.dto.AnnouncementDto;
import site.timecapsulearchive.core.domain.announcement.repository.AnnouncementRepository;

class AnnouncementServiceTest {

private final AnnouncementRepository announcementRepository = mock(
AnnouncementRepository.class);
private final AnnouncementService announcementService = new AnnouncementService(
announcementRepository);

@Test
void 공지사항을_조회하면_가장_최근에_생성된_공지사항이_최상단에_위치한다() {
//given
given(announcementRepository.findAll()).willReturn(AnnouncementTestFixture.announcementDtos(10));

//when
List<AnnouncementDto> announcements = announcementService.findAll();

//then
assertThat(announcements)
.isSortedAccordingTo(Comparator.comparing(AnnouncementDto::createdAt).reversed());
}
}
Loading