Skip to content

Commit 6a89558

Browse files
committed
feat: 뉴스 CRUD 리팩토링
- 등록, 전체 조회(사진 제외), 상세 조회, 수정(사진 제외), 삭제 구현 완료 - 썸네일 포함한 전체 조회, 사진 관련 수정 기능 구현 예정
1 parent 1a63c71 commit 6a89558

File tree

6 files changed

+88
-83
lines changed

6 files changed

+88
-83
lines changed
Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package dmu.dasom.api.domain.news.controller;
22

3+
import dmu.dasom.api.domain.news.dto.NewsCreationResponseDto;
34
import dmu.dasom.api.domain.news.dto.NewsRequestDto;
45
import dmu.dasom.api.domain.news.dto.NewsResponseDto;
56
import dmu.dasom.api.domain.news.service.NewsService;
67
import io.swagger.v3.oas.annotations.tags.Tag;
78
import io.swagger.v3.oas.annotations.Operation;
9+
import jakarta.validation.constraints.Min;
10+
import lombok.RequiredArgsConstructor;
811
import org.springframework.http.ResponseEntity;
912
import org.springframework.web.bind.annotation.*;
1013

@@ -13,44 +16,42 @@
1316

1417
@Tag(name = "NEWS API", description = "뉴스 API")
1518
@RestController
19+
@RequiredArgsConstructor
1620
@RequestMapping("/api/news")
1721
public class NewsController {
1822

1923
private final NewsService newsService;
2024

21-
public NewsController(NewsService newsService) {
22-
this.newsService = newsService;
23-
}
24-
25-
@Operation(summary = "전체 뉴스 조회")
25+
@Operation(summary = "전체 뉴스 조회 (썸네일 포함)")
2626
@GetMapping
2727
public ResponseEntity<List<NewsResponseDto>> getAllNews() {
2828
return ResponseEntity.ok(newsService.getAllNews());
2929
}
3030

31-
@Operation(summary = "개별 뉴스 조회")
31+
@Operation(summary = "뉴스 상세 조회")
3232
@GetMapping("/{id}")
33-
public ResponseEntity<NewsResponseDto> getNewsById(@PathVariable Long id) {
33+
public ResponseEntity<NewsResponseDto> getNewsById(@PathVariable @Min(1) Long id) {
3434
return ResponseEntity.ok(newsService.getNewsById(id));
3535
}
3636

3737
@Operation(summary = "뉴스 등록")
3838
@PostMapping
39-
public ResponseEntity<NewsResponseDto> createNews(@Valid @RequestBody NewsRequestDto requestDto) {
39+
public ResponseEntity<NewsCreationResponseDto> createNews(@Valid @RequestBody NewsRequestDto requestDto) {
4040
return ResponseEntity.status(201).body(newsService.createNews(requestDto));
4141
}
4242

4343
@Operation(summary = "뉴스 수정")
4444
@PutMapping("/{id}")
45-
public ResponseEntity<NewsResponseDto> updateNews(@PathVariable Long id, @Valid @RequestBody NewsRequestDto requestDto) {
45+
public ResponseEntity<NewsResponseDto> updateNews(@PathVariable @Min(1) Long id,
46+
@Valid @RequestBody NewsRequestDto requestDto) {
4647
return ResponseEntity.ok(newsService.updateNews(id, requestDto));
4748
}
4849

4950
@Operation(summary = "뉴스 삭제")
5051
@DeleteMapping("/{id}")
5152
public ResponseEntity<Void> deleteNews(@PathVariable Long id) {
5253
newsService.deleteNews(id);
53-
return ResponseEntity.noContent().build();
54+
return ResponseEntity.ok().build();
5455
}
5556

56-
}
57+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dmu.dasom.api.domain.news.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.NotNull;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
@Schema(name = "NewsCreationResponseDto", description = "뉴스 생성 응답 DTO")
13+
public class NewsCreationResponseDto {
14+
15+
@NotNull
16+
@Schema(description = "뉴스 ID", example = "1")
17+
private Long id;
18+
19+
}

src/main/java/dmu/dasom/api/domain/news/dto/NewsRequestDto.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package dmu.dasom.api.domain.news.dto;
22

3+
import dmu.dasom.api.domain.news.entity.NewsEntity;
34
import io.swagger.v3.oas.annotations.media.Schema;
45
import jakarta.validation.constraints.NotBlank;
56
import jakarta.validation.constraints.Size;
67
import lombok.AllArgsConstructor;
78
import lombok.Getter;
89
import lombok.NoArgsConstructor;
9-
import org.springframework.web.multipart.MultipartFile;
10-
11-
import java.util.List;
1210

1311
@Getter
1412
@NoArgsConstructor
@@ -25,7 +23,11 @@ public class NewsRequestDto {
2523
@Schema(description = "뉴스 내용", example = "새로운 뉴스 내용")
2624
private String content;
2725

28-
@Schema(description = "이미지 파일 ID 목록", example = "[1, 2, 3]", nullable = true)
29-
private List<Long> fileIds;
26+
public NewsEntity toEntity() {
27+
return NewsEntity.builder()
28+
.title(this.title)
29+
.content(this.content)
30+
.build();
31+
}
3032

31-
}
33+
}
Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package dmu.dasom.api.domain.news.dto;
22

3+
import dmu.dasom.api.global.file.dto.FileResponseDto;
34
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.AllArgsConstructor;
46
import lombok.Builder;
57
import lombok.Getter;
8+
import lombok.NoArgsConstructor;
69

710
import java.time.LocalDateTime;
811
import java.util.List;
912

1013
@Getter
14+
@NoArgsConstructor
15+
@AllArgsConstructor
1116
@Builder
1217
@Schema(name = "NewsResponseDto", description = "뉴스 응답 DTO")
1318
public class NewsResponseDto {
@@ -24,15 +29,7 @@ public class NewsResponseDto {
2429
@Schema(description = "작성일", example = "2025-02-14T12:00:00")
2530
private LocalDateTime createdAt;
2631

27-
@Schema(description = "Base64 인코딩된 이미지", example = "[data:image/jpeg;base64,xxxxxx]", nullable = true)
28-
private List<String> imageUrls;
32+
@Schema(description = "인코딩된 이미지", example = "asdf", nullable = true)
33+
private List<FileResponseDto> images;
2934

30-
public NewsResponseDto(Long id, String title, String content, LocalDateTime createdAt, List<String> imageUrls) {
31-
this.id = id;
32-
this.title = title;
33-
this.content = content;
34-
this.createdAt = createdAt;
35-
this.imageUrls = imageUrls;
36-
}
37-
38-
}
35+
}

src/main/java/dmu/dasom/api/domain/news/entity/NewsEntity.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import dmu.dasom.api.domain.common.BaseEntity;
44
import dmu.dasom.api.domain.news.dto.NewsResponseDto;
5-
import dmu.dasom.api.global.file.entity.FileEntity;
5+
import dmu.dasom.api.global.file.dto.FileResponseDto;
66
import io.swagger.v3.oas.annotations.media.Schema;
77
import jakarta.persistence.*;
88
import lombok.*;
9+
import org.apache.commons.lang3.ObjectUtils;
910

10-
import java.util.ArrayList;
1111
import java.util.List;
1212

1313
@Getter
@@ -30,22 +30,29 @@ public class NewsEntity extends BaseEntity {
3030
@Column(nullable = false)
3131
private String content;
3232

33-
@ElementCollection
34-
@CollectionTable(name = "news_images", joinColumns = @JoinColumn(name = "news_id"))
35-
@Column(name = "image_data", columnDefinition = "TEXT")
36-
private List<String> imageUrls;
37-
38-
public void update(String title, String content, List<String> imageUrls) {
33+
public void update(String title, String content) {
3934
this.title = title;
4035
this.content = content;
41-
this.imageUrls = imageUrls;
4236
}
4337

44-
public NewsResponseDto toResponseDto() {
45-
return new NewsResponseDto(id, title, content, getCreatedAt(), imageUrls);
38+
public NewsResponseDto toResponseDto(List<FileResponseDto> images) {
39+
return NewsResponseDto.builder()
40+
.id(this.id)
41+
.title(this.title)
42+
.content(this.content)
43+
.createdAt(getCreatedAt())
44+
.images(ObjectUtils.isEmpty(images) ? null : images)
45+
.build();
4646
}
4747

48-
@OneToMany(mappedBy = "news", cascade = CascadeType.ALL, orphanRemoval = true)
49-
private List<FileEntity> images = new ArrayList<>();
48+
public NewsResponseDto toResponseDto() {
49+
return NewsResponseDto.builder()
50+
.id(this.id)
51+
.title(this.title)
52+
.content(this.content)
53+
.createdAt(getCreatedAt())
54+
.images(null)
55+
.build();
56+
}
5057

5158
}

src/main/java/dmu/dasom/api/domain/news/service/NewsService.java

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package dmu.dasom.api.domain.news.service;
22

3-
import dmu.dasom.api.global.file.dto.FileResponseDto;
4-
import dmu.dasom.api.global.file.entity.FileEntity;
3+
import dmu.dasom.api.domain.news.dto.NewsCreationResponseDto;
4+
import dmu.dasom.api.global.file.enums.FileType;
55
import dmu.dasom.api.global.file.service.FileService;
66
import dmu.dasom.api.domain.common.exception.CustomException;
77
import dmu.dasom.api.domain.common.exception.ErrorCode;
@@ -14,58 +14,43 @@
1414
import org.springframework.transaction.annotation.Transactional;
1515

1616
import java.util.List;
17-
import java.util.stream.Collectors;
1817

1918
@Service
2019
@RequiredArgsConstructor
20+
@Transactional(readOnly = true)
2121
public class NewsService {
2222

2323
private final NewsRepository newsRepository;
2424
private final FileService fileService;
2525

2626
// 전체 뉴스 조회
2727
public List<NewsResponseDto> getAllNews() {
28-
return newsRepository.findAll().stream()
29-
.map(news -> NewsResponseDto.builder()
30-
.id(news.getId())
31-
.title(news.getTitle())
32-
.content(news.getContent())
33-
.createdAt(news.getCreatedAt())
34-
.imageUrls(news.getImageUrls())
35-
.build())
36-
.collect(Collectors.toList());
28+
List<NewsEntity> news = newsRepository.findAll();
29+
30+
// List<FileResponseDto> files = fileService.getFirstFileByTypeAndTargetId(
31+
// FileType.NEWS,
32+
// news.stream()
33+
// .map(NewsEntity::getId)
34+
// .toList()
35+
// );
36+
37+
return news.stream()
38+
.map(NewsEntity::toResponseDto)
39+
.toList();
3740
}
3841

3942
// 개별 뉴스 조회
4043
public NewsResponseDto getNewsById(Long id) {
4144
NewsEntity news = newsRepository.findById(id)
4245
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
4346

44-
return NewsResponseDto.builder()
45-
.id(news.getId())
46-
.title(news.getTitle())
47-
.content(news.getContent())
48-
.createdAt(news.getCreatedAt())
49-
.imageUrls(news.getImageUrls())
50-
.build();
47+
return news.toResponseDto(fileService.getFilesByTypeAndTargetId(FileType.NEWS, id));
5148
}
5249

53-
// 뉴스 생성
50+
// 뉴스 생성 (생성된 뉴스 ID 반환)
5451
@Transactional
55-
public NewsResponseDto createNews(NewsRequestDto requestDto) {
56-
List<FileResponseDto> uploadedFiles = fileService.getFilesByIds(requestDto.getFileIds());
57-
58-
List<String> base64Images = uploadedFiles.stream()
59-
.map(file -> "data:" + file.getFileType() + ";base64," + file.getBase64Data())
60-
.collect(Collectors.toList());
61-
62-
NewsEntity news = NewsEntity.builder()
63-
.title(requestDto.getTitle())
64-
.content(requestDto.getContent())
65-
.imageUrls(base64Images)
66-
.build();
67-
68-
return newsRepository.save(news).toResponseDto();
52+
public NewsCreationResponseDto createNews(NewsRequestDto requestDto) {
53+
return new NewsCreationResponseDto(newsRepository.save(requestDto.toEntity()).getId());
6954
}
7055

7156
// 뉴스 수정
@@ -74,14 +59,7 @@ public NewsResponseDto updateNews(Long id, NewsRequestDto requestDto) {
7459
NewsEntity news = newsRepository.findById(id)
7560
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
7661

77-
// fileEntity -> response로 수정
78-
List<FileResponseDto> uploadedFiles = fileService.getFilesByIds(requestDto.getFileIds());
79-
80-
List<String> base64Images = uploadedFiles.stream()
81-
.map(file -> "data:" + file.getFileType() + ";base64," + file.getBase64Data())
82-
.collect(Collectors.toList());
83-
84-
news.update(requestDto.getTitle(), requestDto.getContent(), base64Images);
62+
news.update(requestDto.getTitle(), requestDto.getContent());
8563

8664
return news.toResponseDto();
8765
}
@@ -92,6 +70,7 @@ public void deleteNews(Long id) {
9270
NewsEntity news = newsRepository.findById(id)
9371
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
9472

73+
fileService.deleteFilesByTypeAndTargetId(FileType.NEWS, news.getId());
9574
newsRepository.delete(news);
9675
}
9776

0 commit comments

Comments
 (0)