Skip to content

Commit

Permalink
Feat/#36 공지사항 게시판 (#37)
Browse files Browse the repository at this point in the history
* rebase

* rebase

* #36 refactor(AnnouncementService): @validated 추가, ModelMapper 제거

* #36 refactor(AnnouncementCommand): DTO 리팩토링

* #36 refactor(AnnouncementService): 서비스 리팩토링

* #36 refactor(AnnouncementController): 컨트롤러 리팩토링

* #36 test(AnnouncementServiceTest): 테스트코드 추가

* #36 refactor(AnnouncementService): 유효성 검사 추가

* Fix test
  • Loading branch information
yuna83 authored Jan 16, 2025
1 parent 3cc6bfa commit 287a437
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 0 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ dependencies {


implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'
//객체 간 매핑 처리
implementation 'org.modelmapper:modelmapper:3.1.0'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.api.announcement;

import com.example.api.domain.Announcement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AnnouncementRepository extends JpaRepository<Announcement, Long> {
List<Announcement> findByAnnouncementTitleContaining(final String keyword);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.example.api.announcement;

import com.example.api.announcement.dto.AnnouncementCommand;
import com.example.api.announcement.dto.AnnouncementResponse;
import com.example.api.domain.Announcement;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class AnnouncementService {
private final AnnouncementRepository announcementRepository;

@Transactional
public AnnouncementResponse createAnnouncement(
@Validated final AnnouncementCommand command
) {
Announcement announcement = new Announcement();
announcement.setAnnouncementTitle(command.announcementTitle());
announcement.setAnnouncementType(command.announcementType());
announcement.setAnnouncementContent(command.announcementContent());
Announcement savedAnnouncement = announcementRepository.save(announcement);
return new AnnouncementResponse(savedAnnouncement);
}

@Transactional
public List<AnnouncementResponse> getAllAnnouncements() {
final List<Announcement> announcements = announcementRepository.findAll();
return announcements.stream()
.map(AnnouncementResponse::new)
.collect(Collectors.toList());
}

@Transactional
public AnnouncementResponse getAnnouncement(
@Validated final Long announcementId
) {
final Announcement announcement = findAnnouncementById(announcementId);
return new AnnouncementResponse(announcement);
}

@Transactional
public AnnouncementResponse updateAnnouncement(
@Validated final Long announcementId,
@Validated final AnnouncementCommand command
) {
Announcement announcement = findAnnouncementById(announcementId);
announcement.setAnnouncementTitle(command.announcementTitle());
announcement.setAnnouncementType(command.announcementType());
announcement.setAnnouncementContent(command.announcementContent());
Announcement updatedAnnouncement = announcementRepository.save(announcement);
return new AnnouncementResponse(updatedAnnouncement);
}

@Transactional
public void deleteAnnouncement(
@Validated final Long announcementId
) {
final Announcement announcement = findAnnouncementById(announcementId);
announcementRepository.delete(announcement);
}

@Transactional
public List<AnnouncementResponse> searchAnnouncements(
@Validated final String keyword
) {
final List<Announcement> announcements = announcementRepository.findByAnnouncementTitleContaining(keyword);
return announcements.stream()
.map(AnnouncementResponse::new)
.collect(Collectors.toList());
}

private Announcement findAnnouncementById(
@Validated final Long announcementId
) {
return announcementRepository.findById(announcementId)
.orElseThrow(() -> new RuntimeException(getErrorMessage("announcement.not.found")));
}

private String getErrorMessage(final String key) {
return key;
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.example.api.announcement.controller;

import com.example.api.announcement.AnnouncementService;
import com.example.api.announcement.dto.AnnouncementCommand;
import com.example.api.announcement.dto.AnnouncementRequest;
import com.example.api.announcement.dto.AnnouncementResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/support/announcements")
public class AnnouncementController {
private final AnnouncementService announcementService;

@PostMapping
public ResponseEntity<AnnouncementResponse> createAnnouncement(
@RequestBody final AnnouncementRequest request
) {
final AnnouncementCommand command = request.toCommand();
final AnnouncementResponse response = announcementService.createAnnouncement(command);
return ResponseEntity.ok(response);
}

@GetMapping
public ResponseEntity<List<AnnouncementResponse>> getAnnouncements() {
final List<AnnouncementResponse> responses = announcementService.getAllAnnouncements();
return ResponseEntity.ok(responses);
}

@GetMapping("/{announcementId}")
public ResponseEntity<AnnouncementResponse> getAnnouncement(
@PathVariable(required = true) final Long announcementId
) {
final AnnouncementResponse response = announcementService.getAnnouncement(announcementId);
return ResponseEntity.ok(response);
}

@PutMapping("/{announcementId}")
public ResponseEntity<AnnouncementResponse> updateAnnouncement(
@PathVariable(required = true) final Long announcementId,
@RequestBody final AnnouncementRequest request
) {
final AnnouncementCommand command = request.toCommand();
final AnnouncementResponse response = announcementService.updateAnnouncement(
announcementId, command);
return ResponseEntity.ok(response);
}

@DeleteMapping("/{announcementId}")
public ResponseEntity<Void> deleteAnnouncement(
@PathVariable(required = true) final Long announcementId
) {
announcementService.deleteAnnouncement(announcementId);
return ResponseEntity.ok().build();
}

@GetMapping("/search")
public ResponseEntity<List<AnnouncementResponse>> searchAnnouncements(
@RequestParam(required = true) final String keyword
) {
final List<AnnouncementResponse> responses = announcementService.searchAnnouncements(keyword);
return ResponseEntity.ok(responses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.api.announcement.dto;

import lombok.NonNull;

public record AnnouncementCommand(
@NonNull
String announcementTitle,
String announcementType,
String announcementContent
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.api.announcement.dto;

import lombok.NonNull;

public record AnnouncementRequest(
@NonNull
String announcementTitle,
String announcementType,
String announcementContent
) {
public AnnouncementCommand toCommand() {
return new AnnouncementCommand(
this.announcementTitle,
this.announcementType,
this.announcementContent
);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.api.announcement.dto;

import com.example.api.domain.Announcement;

public record AnnouncementResponse(
Long announcementId,
String announcementTitle,
String announcementType,
String announcementContent,
int viewCount
) {
public AnnouncementResponse(Announcement announcement) {
this(
announcement.getAnnouncementId(),
announcement.getAnnouncementTitle(),
announcement.getAnnouncementType(),
announcement.getAnnouncementContent(),
announcement.getViewCount()
);
}
}

3 changes: 3 additions & 0 deletions src/main/java/com/example/api/domain/Announcement.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import jakarta.persistence.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;

@Entity
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
@Table(name = "ANNOUNCEMENT")
public class Announcement extends BaseEntity {
@NonNull
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long announcementId;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.example.api.announcement;

import com.example.api.announcement.controller.AnnouncementController;
import com.example.api.announcement.dto.AnnouncementRequest;
import com.example.api.announcement.dto.AnnouncementResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.ResponseEntity;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

class AnnouncementControllerTest {
@InjectMocks
private AnnouncementController announcementController;
@Mock
private AnnouncementService announcementService;
private static final String DEFAULT_TITLE = "공지사항 제목";
private static final String DEFAULT_TYPE = "공지사항";
private static final String DEFAULT_CONTENT = "공지사항 내용";
private static final int DEFAULT_VIEW_COUNT = 100;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}

@Test
void createAnnouncement_success() {
final AnnouncementRequest request = createMockRequest();
final AnnouncementResponse response = createMockResponse();
when(announcementService.createAnnouncement(any()))
.thenReturn(response);
ResponseEntity<AnnouncementResponse> result = announcementController.createAnnouncement(request);
assertCreateAnnouncementResponse(result);
verify(announcementService, times(1)).createAnnouncement(any());
}

@Test
void getAnnouncements_success() {
final AnnouncementResponse response = createMockResponse();
when(announcementService.getAllAnnouncements())
.thenReturn(List.of(response));
ResponseEntity<List<AnnouncementResponse>> result = announcementController.getAnnouncements();
assertGetAnnouncementsResponse(result);
verify(announcementService, times(1)).getAllAnnouncements();
}

private AnnouncementRequest createMockRequest() {
return new AnnouncementRequest(DEFAULT_TITLE, DEFAULT_TYPE, DEFAULT_CONTENT);
}

private AnnouncementResponse createMockResponse() {
return new AnnouncementResponse(1L, DEFAULT_TITLE, DEFAULT_TYPE, DEFAULT_CONTENT, DEFAULT_VIEW_COUNT);
}

private void assertCreateAnnouncementResponse(ResponseEntity<AnnouncementResponse> result) {
assertThat(result.getStatusCodeValue()).isEqualTo(200);
assertThat(result.getBody().announcementTitle()).isEqualTo(DEFAULT_TITLE);
}

private void assertGetAnnouncementsResponse(ResponseEntity<List<AnnouncementResponse>> result) {
assertThat(result.getStatusCodeValue()).isEqualTo(200);
assertThat(result.getBody()).hasSize(1);
}
}
Loading

0 comments on commit 287a437

Please sign in to comment.