Skip to content

Commit

Permalink
Merge pull request #240 from urinaner/feat/238
Browse files Browse the repository at this point in the history
[BE] [FEAT] 뉴스 CRUD API 생성
  • Loading branch information
urinaner authored Jan 3, 2025
2 parents d12d03a + c0cf104 commit 2180d29
Show file tree
Hide file tree
Showing 10 changed files with 444 additions and 2 deletions.
15 changes: 14 additions & 1 deletion backend/backup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ DROP TABLE IF EXISTS `admin_SEQ`;
DROP TABLE IF EXISTS `admin`;
DROP TABLE IF EXISTS `UserEntity`;
DROP TABLE IF EXISTS `professor_SEQ`;
DROP TABLE IF EXISTS `news`;

-- Admin table
CREATE TABLE `admin` (
Expand Down Expand Up @@ -119,4 +120,16 @@ CREATE TABLE `reservation` (
CONSTRAINT `FK_reservation_seminar_room` FOREIGN KEY (`seminar_room_id`) REFERENCES `seminar_room` (`seminar_room_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;

-- Continue for other tables like `thesis`, `board`, and `users`
-- Continue for other tables like `thesis`, `board`, and `users`

-- News 테이블 생성
CREATE TABLE `news` (
`news_id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255),
`content` TEXT,
`view` INT DEFAULT 0,
`link` VARCHAR(255),
`image` VARCHAR(1000),
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`news_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.example.backend.news.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.example.backend.common.dto.PageRequestDto;
import org.example.backend.common.dto.ResponseDto;
import org.example.backend.news.domain.dto.NewsReqDto;
import org.example.backend.news.domain.dto.NewsResDto;
import org.example.backend.news.service.NewsService;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequiredArgsConstructor
@Tag(name = "뉴스", description = "뉴스 API")
@RequestMapping("/api/news")
public class NewsController {
private final NewsService newsService;

@Operation(summary = "뉴스 생성 API", description = "뉴스 생성")
@PostMapping(consumes = "multipart/form-data")
public ResponseEntity<Long> createNews(
@RequestPart(value = "newsReqDto") @Valid NewsReqDto newsReqDto,
@RequestPart(value = "newsImage", required = false) MultipartFile multipartFile
) {
Long newsId = newsService.saveNews(newsReqDto, multipartFile);
return new ResponseEntity<>(newsId, HttpStatus.OK);
}

@Operation(summary = "모든 뉴스 조회 API", description = "모든 뉴스의 리스트 반환")
@GetMapping
public ResponseDto<List<NewsResDto>> getAllBoards(@Valid @ModelAttribute PageRequestDto pageRequest) {

Page<NewsResDto> newsList = newsService.getAllNewss(pageRequest.toPageable());
return ResponseDto.ok(newsList.getNumber(), newsList.getTotalPages(), newsList.getContent());
}

@Operation(summary = "단일 뉴스 조회 API", description = "단일 뉴스의 리스트 반환")
@GetMapping("/{newsId}")
public ResponseEntity<NewsResDto> getNews(@PathVariable(name = "newsId") Long newsId) {
NewsResDto newsResDto = newsService.getNews(newsId);
return new ResponseEntity<>(newsResDto, HttpStatus.OK);
}

@Operation(summary = "뉴스 정보 업데이트 API", description = "뉴스 정보 업데이트")
@PostMapping("/{newsId}")
public ResponseEntity<NewsResDto> updateNews(@PathVariable(name = "newsId") Long newsId,
@RequestPart(value = "newsReqDto") NewsReqDto newsReqDto,
@RequestPart(value = "newsImage", required = false) MultipartFile multipartFile) {
NewsResDto newsResDto = newsService.updateNews(newsId, newsReqDto, multipartFile);
return new ResponseEntity<>(newsResDto, HttpStatus.OK);
}

@Operation(summary = "뉴스 삭제 API", description = "뉴스 삭제")
@DeleteMapping("/{newsId}")
public ResponseEntity<?> deleteNews(@PathVariable(name = "newsId") Long newsId) {
newsService.deleteNews(newsId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.example.backend.news.domain.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

@NotBlank(message = "제목은 필수 입력값입니다.")
@Size(max = 200, message = "제목은 최대 200자 입력 가능합니다.")
private String title;

@NotBlank(message = "내용은 필수 입력값입니다.")
@Size(max = 5000, message = "내용은 최대 5000자까지 입력 가능합니다.")
private String content;

private String createDate;
private String link;
private String image;

@Builder
private NewsReqDto(String title, String content, String createDate,
String link, String image) {
this.title = title;
this.content = content;
this.createDate = createDate;
this.link = link;
this.image = image;
}

public static NewsReqDto of(String title, String content, String createDate,
String link, String image) {
return NewsReqDto.builder()
.title(title)
.content(content)
.createDate(createDate)
.link(link)
.image(image)
.build();
}

public void setImage(String image) {
this.image = image;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.example.backend.news.domain.dto;

import java.time.format.DateTimeFormatter;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.backend.news.domain.entity.News;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class NewsResDto {
private Long id;
private String title;
private String content;
private int view;
private String createDate;
private String link;
private String image;

@Builder
private NewsResDto(Long id, String title, int view, String content, String createDate,
String link, String image) {
this.id = id;
this.title = title;
this.content = content;
this.view = view;
this.createDate = createDate;
this.link = link;
this.image = image;
}

public static NewsResDto of(News news) {
return NewsResDto.builder()
.id(news.getId())
.title(news.getTitle())
.content(news.getContent())
.view(news.getView())
.createDate(news.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.link(news.getLink())
.image(news.getImage())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.example.backend.news.domain.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.backend.common.domain.BaseEntity;
import org.example.backend.news.domain.dto.NewsReqDto;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class News extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "news_id", nullable = false)
private Long id;

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

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

@Column(name = "view")
private int view;

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

@Column(name = "image", length = 1000)
private String image;


@Builder
private News(String title, String content, int view, String link,
String image) {
this.title = title;
this.content = content;
this.view = view;
this.link = link;
this.image = image;
}

public static News of(NewsReqDto dto) {
return News.builder()
.title(dto.getTitle())
.content(dto.getContent())
.view(0)
.link(dto.getLink())
.image(dto.getImage())
.build();
}

public void update(NewsReqDto dto) {
this.title = dto.getTitle();
this.content = dto.getContent();
this.link = dto.getLink();
this.image = dto.getImage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.example.backend.news.exception;

import lombok.RequiredArgsConstructor;
import org.example.backend.common.exception.BaseException;
import org.example.backend.common.exception.BaseExceptionType;

@RequiredArgsConstructor
public class NewsException extends BaseException {

private final NewsExceptionType exceptionType;

@Override
public BaseExceptionType exceptionType() {
return exceptionType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.example.backend.news.exception;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;

import lombok.RequiredArgsConstructor;
import org.example.backend.common.exception.BaseExceptionType;
import org.springframework.http.HttpStatus;

@RequiredArgsConstructor
public enum NewsExceptionType implements BaseExceptionType {

NOT_FOUND_NEWS(NOT_FOUND, "뉴스를 찾을 수 없습니다")
;

private final HttpStatus httpStatus;
private final String errorMessage;

@Override
public HttpStatus httpStatus() {
return httpStatus;
}

@Override
public String errorMessage() {
return errorMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.example.backend.news.repository;

import org.example.backend.news.domain.entity.News;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NewsRepository extends JpaRepository<News, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.example.backend.news.service;

import static org.example.backend.news.exception.NewsExceptionType.NOT_FOUND_NEWS;

import lombok.RequiredArgsConstructor;
import org.example.backend.global.config.aws.S3Uploader;
import org.example.backend.news.domain.dto.NewsReqDto;
import org.example.backend.news.domain.dto.NewsResDto;
import org.example.backend.news.domain.entity.News;
import org.example.backend.news.exception.NewsException;
import org.example.backend.news.exception.NewsExceptionType;
import org.example.backend.news.repository.NewsRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class NewsService {
private final NewsRepository newsRepository;
private final S3Uploader s3Uploader;
private static final String dirName = "news";

@Transactional
public Long saveNews(NewsReqDto newsReqDto, MultipartFile multipartFile) {

if (multipartFile != null && !multipartFile.isEmpty()) {
String uploadImageUrl = s3Uploader.upload(multipartFile, dirName);
newsReqDto.setImage(uploadImageUrl);
}
News news = News.of(newsReqDto);
News savedNews = newsRepository.save(news);
return savedNews.getId();
}

public NewsResDto getNews(Long newsId) {
News news = findNewsById(newsId);
return NewsResDto.of(news);
}

@Transactional
public NewsResDto updateNews(Long newsId, NewsReqDto newsReqDto, MultipartFile multipartFile) {
if (multipartFile != null && !multipartFile.isEmpty()) {
String uploadImageUrl = s3Uploader.upload(multipartFile, dirName);
newsReqDto.setImage(uploadImageUrl);
}

News news = findNewsById(newsId);
news.update(newsReqDto);
return NewsResDto.of(news);
}

public void deleteNews(Long newsId) {
News news = findNewsById(newsId);
newsRepository.delete(news);
}

private News findNewsById(Long newsId) {
return newsRepository.findById(newsId)
.orElseThrow(() -> new NewsException(NOT_FOUND_NEWS));
}

public Page<NewsResDto> getAllNewss(Pageable pageable) {
return newsRepository.findAll(pageable)
.map(NewsResDto::of);
}
}
Loading

0 comments on commit 2180d29

Please sign in to comment.