-
Notifications
You must be signed in to change notification settings - Fork 8
feat: 소식지 서비스 구현 #337
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
Closed
Gyuhyeok99
wants to merge
19
commits into
solid-connection:develop
from
Gyuhyeok99:feat/300-news-api
Closed
feat: 소식지 서비스 구현 #337
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
64a12ed
feat: 소식지 생성 api 추가
Gyuhyeok99 43048bc
test: 소식지 생성 테스트 추가
Gyuhyeok99 b6316ee
refactor: @NotBlank 적용 및 검증 메시지 개선
Gyuhyeok99 657c0a9
feat: 소식지 수정 api 추가
Gyuhyeok99 af29b51
feat: NewsFixture에 News 생성 메서드 추가
Gyuhyeok99 958b57d
test: 소식지 수정 테스트 추가
Gyuhyeok99 e7b42af
feat: 소식지 삭제 api 추가
Gyuhyeok99 30f0b45
test: 소식지 삭제 테스트 추가
Gyuhyeok99 01864cc
refactor: NewsService → NewsCommandService로 이름 변경
Gyuhyeok99 75ad00d
feat: 소식지 목록 조회 api 추가
Gyuhyeok99 d370832
test: 소식지 목록 조회 테스트 추가
Gyuhyeok99 ea0bf83
feat: 소식지 조회 api 추가
Gyuhyeok99 780e1f3
test: 소식지 조회 테스트 추가
Gyuhyeok99 78c44e0
fix: 소식지 설명 길이 제한 메시지 오타 수정
Gyuhyeok99 a9c030b
fix: 소식지 수정 및 삭제 시 S3 이미지 삭제 시점 수정
Gyuhyeok99 6bfdd95
test: NewsCommandServiceTest public 제거
Gyuhyeok99 c792f2f
fix: 소식지 수정 시 S3 이미지 삭제 시점 수정
Gyuhyeok99 4764af0
refactor: 소식지 업데이트 시 개별 파라미터에서 DTO로 변경
Gyuhyeok99 0a13606
test: 소식지 업데이트 테스트 시 assertAll로 그룹화
Gyuhyeok99 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
src/main/java/com/example/solidconnection/news/controller/NewsController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| package com.example.solidconnection.news.controller; | ||
|
|
||
| import com.example.solidconnection.common.resolver.AuthorizedUser; | ||
| import com.example.solidconnection.news.dto.NewsCreateRequest; | ||
| import com.example.solidconnection.news.dto.NewsCommandResponse; | ||
| import com.example.solidconnection.news.dto.NewsFindResponse; | ||
| import com.example.solidconnection.news.dto.NewsResponse; | ||
| import com.example.solidconnection.news.dto.NewsUpdateRequest; | ||
| import com.example.solidconnection.news.service.NewsCommandService; | ||
| import com.example.solidconnection.news.service.NewsQueryService; | ||
| import com.example.solidconnection.security.annotation.RequireAdminAccess; | ||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| 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.PatchMapping; | ||
| 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.RequestParam; | ||
| import org.springframework.web.bind.annotation.RequestPart; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/news") | ||
| public class NewsController { | ||
|
|
||
| private final NewsQueryService newsQueryService; | ||
| private final NewsCommandService newsCommandService; | ||
|
|
||
| // todo: 추후 검색 조건 및 Slice 적용 | ||
| @GetMapping | ||
| public ResponseEntity<NewsResponse> searchNews() { | ||
| NewsResponse newsResponse = newsQueryService.searchNews(); | ||
| return ResponseEntity.ok(newsResponse); | ||
| } | ||
|
|
||
| @GetMapping(value = "/{news_id}") | ||
| public ResponseEntity<NewsFindResponse> findNewsById( | ||
| @AuthorizedUser SiteUser siteUser, | ||
| @PathVariable("news_id") Long newsId | ||
| ) { | ||
| NewsFindResponse newsFindResponse = newsQueryService.findNewsById(newsId); | ||
| return ResponseEntity.ok(newsFindResponse); | ||
| } | ||
|
|
||
| @RequireAdminAccess | ||
| @PostMapping | ||
| public ResponseEntity<NewsCommandResponse> createNews( | ||
| @AuthorizedUser SiteUser siteUser, | ||
| @Valid @RequestPart("newsCreateRequest") NewsCreateRequest newsCreateRequest, | ||
| @RequestParam(value = "file") MultipartFile imageFile | ||
| ) { | ||
| NewsCommandResponse newsCommandResponse = newsCommandService.createNews(newsCreateRequest, imageFile); | ||
| return ResponseEntity.ok(newsCommandResponse); | ||
| } | ||
Gyuhyeok99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @RequireAdminAccess | ||
| @PatchMapping(value = "/{news_id}") | ||
| public ResponseEntity<NewsCommandResponse> updateNews( | ||
| @AuthorizedUser SiteUser siteUser, | ||
| @PathVariable("news_id") Long newsId, | ||
| @Valid @RequestPart(value = "newsUpdateRequest") NewsUpdateRequest newsUpdateRequest, | ||
| @RequestParam(value = "file", required = false) MultipartFile imageFile | ||
| ) { | ||
| NewsCommandResponse newsCommandResponse = newsCommandService.updateNews(newsId, newsUpdateRequest, imageFile); | ||
| return ResponseEntity.ok(newsCommandResponse); | ||
| } | ||
|
|
||
| @RequireAdminAccess | ||
| @DeleteMapping(value = "/{news_id}") | ||
| public ResponseEntity<NewsCommandResponse> deleteNewsById( | ||
| @AuthorizedUser SiteUser siteUser, | ||
| @PathVariable("news_id") Long newsId | ||
| ) { | ||
| NewsCommandResponse newsCommandResponse = newsCommandService.deleteNewsById(newsId); | ||
| return ResponseEntity.ok(newsCommandResponse); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.example.solidconnection.news.dto; | ||
|
|
||
| import com.example.solidconnection.news.domain.News; | ||
|
|
||
| public record NewsCommandResponse( | ||
| long id | ||
| ) { | ||
| public static NewsCommandResponse from(News news) { | ||
| return new NewsCommandResponse( | ||
| news.getId() | ||
| ); | ||
| } | ||
| } |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.example.solidconnection.news.dto; | ||
|
|
||
| import com.example.solidconnection.news.domain.News; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.Size; | ||
|
|
||
| public record NewsCreateRequest( | ||
| @NotBlank(message = "소식지 제목을 입력해주세요.") | ||
| @Size(max = 255, message = "소식지 제목은 최대 255자 이하여야 합니다.") | ||
| String title, | ||
|
|
||
| @NotBlank(message = "소식지 설명을 입력해주세요.") | ||
| @Size(max = 255, message = "소식지 설명은 최대 255자 이하여야 합니다.") | ||
| String description, | ||
|
|
||
| @NotBlank(message = "소식지 URL을 입력해주세요.") | ||
| @Size(max = 500, message = "소식지 URL은 최대 500자 이하여야 합니다.") | ||
| String url | ||
| ) { | ||
| public News toEntity(String thumbnailUrl) { | ||
| return new News( | ||
| title, | ||
| description, | ||
| thumbnailUrl, | ||
| url | ||
| ); | ||
| } | ||
| } |
27 changes: 27 additions & 0 deletions
27
src/main/java/com/example/solidconnection/news/dto/NewsFindResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package com.example.solidconnection.news.dto; | ||
|
|
||
| import com.example.solidconnection.news.domain.News; | ||
|
|
||
| import java.time.ZonedDateTime; | ||
|
|
||
| public record NewsFindResponse( | ||
| long id, | ||
| String title, | ||
| String description, | ||
| String thumbnailUrl, | ||
| String url, | ||
| ZonedDateTime createdAt, | ||
| ZonedDateTime updatedAt | ||
| ) { | ||
| public static NewsFindResponse from(News news) { | ||
| return new NewsFindResponse( | ||
| news.getId(), | ||
| news.getTitle(), | ||
| news.getDescription(), | ||
| news.getThumbnailUrl(), | ||
| news.getUrl(), | ||
| news.getCreatedAt(), | ||
| news.getUpdatedAt() | ||
| ); | ||
| } | ||
| } |
25 changes: 25 additions & 0 deletions
25
src/main/java/com/example/solidconnection/news/dto/NewsItemResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.example.solidconnection.news.dto; | ||
|
|
||
| import com.example.solidconnection.news.domain.News; | ||
|
|
||
| import java.time.ZonedDateTime; | ||
|
|
||
| public record NewsItemResponse( | ||
| long id, | ||
| String title, | ||
| String thumbnailUrl, | ||
| String url, | ||
| ZonedDateTime createdAt, | ||
| ZonedDateTime updatedAt | ||
| ) { | ||
| public static NewsItemResponse from(News news) { | ||
| return new NewsItemResponse( | ||
| news.getId(), | ||
| news.getTitle(), | ||
| news.getThumbnailUrl(), | ||
| news.getUrl(), | ||
| news.getCreatedAt(), | ||
| news.getUpdatedAt() | ||
| ); | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
src/main/java/com/example/solidconnection/news/dto/NewsResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.solidconnection.news.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record NewsResponse( | ||
| List<NewsItemResponse> newsItemsResponseList | ||
| ) { | ||
| public static NewsResponse from(List<NewsItemResponse> newsItemsResponseList) { | ||
| return new NewsResponse(newsItemsResponseList); | ||
| } | ||
| } |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.example.solidconnection.news.dto; | ||
|
|
||
| import jakarta.validation.constraints.Size; | ||
|
|
||
| public record NewsUpdateRequest( | ||
|
|
||
| @Size(max = 255, message = "소식지 제목은 최대 255자 이하여야 합니다.") | ||
| String title, | ||
|
|
||
| @Size(max = 255, message = "소식지 설명은 최대 255자 이하여야 합니다.") | ||
| String description, | ||
|
|
||
| @Size(max = 500, message = "소식지 URL은 최대 500자 이하여야 합니다.") | ||
| String url | ||
| ) { | ||
| } |
11 changes: 11 additions & 0 deletions
11
src/main/java/com/example/solidconnection/news/repository/NewsRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.solidconnection.news.repository; | ||
|
|
||
| import com.example.solidconnection.news.domain.News; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface NewsRepository extends JpaRepository<News, Long> { | ||
|
|
||
| List<News> findAllByOrderByUpdatedAtDesc(); | ||
| } | ||
Gyuhyeok99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
101 changes: 101 additions & 0 deletions
101
src/main/java/com/example/solidconnection/news/service/NewsCommandService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| package com.example.solidconnection.news.service; | ||
|
|
||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.news.domain.News; | ||
| import com.example.solidconnection.news.dto.NewsCreateRequest; | ||
| import com.example.solidconnection.news.dto.NewsCommandResponse; | ||
| import com.example.solidconnection.news.dto.NewsUpdateRequest; | ||
| import com.example.solidconnection.news.repository.NewsRepository; | ||
| import com.example.solidconnection.s3.domain.ImgType; | ||
| import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; | ||
| import com.example.solidconnection.s3.service.S3Service; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.NEWS_DESCRIPTION_TOO_LONG; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.NEWS_TITLE_EMPTY; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.NEWS_TITLE_TOO_LONG; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.NEWS_URL_INVALID; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.NEWS_URL_TOO_LONG; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class NewsCommandService { | ||
|
|
||
| public static final int MAX_TITLE_LENGTH = 255; | ||
| public static final int MAX_DESCRIPTION_LENGTH = 255; | ||
| public static final int MAX_URL_LENGTH = 500; | ||
|
|
||
| private final S3Service s3Service; | ||
| private final NewsRepository newsRepository; | ||
|
|
||
| @Transactional | ||
| public NewsCommandResponse createNews(NewsCreateRequest newsCreateRequest, MultipartFile imageFile) { | ||
| UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.NEWS); | ||
| News news = newsCreateRequest.toEntity(uploadedFile.fileUrl()); | ||
| News savedNews = newsRepository.save(news); | ||
| return NewsCommandResponse.from(savedNews); | ||
| } | ||
Gyuhyeok99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Transactional | ||
| public NewsCommandResponse updateNews(Long newsId, NewsUpdateRequest newsUpdateRequest, MultipartFile imageFile) { | ||
| News news = newsRepository.findById(newsId) | ||
| .orElseThrow(() -> new CustomException(NEWS_NOT_FOUND)); | ||
| if (newsUpdateRequest.title() != null) { | ||
| validateTitle(newsUpdateRequest.title()); | ||
| news.updateTitle(newsUpdateRequest.title()); | ||
| } | ||
| if (newsUpdateRequest.description() != null) { | ||
| validateDescription(newsUpdateRequest.description()); | ||
| news.updateDescription(newsUpdateRequest.description()); | ||
| } | ||
| if (newsUpdateRequest.url() != null) { | ||
| validateUrl(newsUpdateRequest.url()); | ||
| news.updateUrl(newsUpdateRequest.url()); | ||
| } | ||
| if (imageFile != null) { | ||
| UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.NEWS); | ||
| s3Service.deletePostImage(news.getThumbnailUrl()); | ||
| String thumbnailImageUrl = uploadedFile.fileUrl(); | ||
| news.updateThumbnailUrl(thumbnailImageUrl); | ||
| } | ||
| News savedNews = newsRepository.save(news); | ||
| return NewsCommandResponse.from(savedNews); | ||
| } | ||
|
|
||
| @Transactional | ||
| public NewsCommandResponse deleteNewsById(Long newsId) { | ||
| News news = newsRepository.findById(newsId) | ||
| .orElseThrow(() -> new CustomException(NEWS_NOT_FOUND)); | ||
| newsRepository.deleteById(newsId); | ||
| s3Service.deletePostImage(news.getThumbnailUrl()); | ||
| return NewsCommandResponse.from(news); | ||
| } | ||
|
|
||
| private void validateTitle(String title) { | ||
| if (title.trim().isEmpty()) { | ||
| throw new CustomException(NEWS_TITLE_EMPTY); | ||
| } | ||
| if (title.length() > MAX_TITLE_LENGTH) { | ||
| throw new CustomException(NEWS_TITLE_TOO_LONG); | ||
| } | ||
| } | ||
|
|
||
| private void validateDescription(String description) { | ||
| if (description.length() > MAX_DESCRIPTION_LENGTH) { | ||
| throw new CustomException(NEWS_DESCRIPTION_TOO_LONG); | ||
| } | ||
| } | ||
|
|
||
| private void validateUrl(String url) { | ||
| if (!url.matches("^https?://.*")) { | ||
| throw new CustomException(NEWS_URL_INVALID); | ||
| } | ||
| if (url.length() > MAX_URL_LENGTH) { | ||
| throw new CustomException(NEWS_URL_TOO_LONG); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.