Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e7a3993
refactor: News 예외처리 수정
dohy-eon Feb 24, 2025
ac10bee
refactor: 속성 관련 검증로직 수정
dohy-eon Feb 24, 2025
790a184
fix: ErrorCode 세미콜론추가
dohy-eon Feb 24, 2025
abfcb7b
Merge branch 'dev' into refactor/#47
dohy-eon Feb 24, 2025
dd35ada
feat: 수정삭제 테스트케이스 작성
dohy-eon Feb 25, 2025
30f21f6
Merge for branch 'refactor/#47' of https://github.com/DASOM-GitHub/da…
dohy-eon Feb 25, 2025
04799b7
refactor: 생성 트랜잭션추가
dohy-eon Feb 25, 2025
e125d4e
refactor: 404응답처리 400번으로 수정
dohy-eon Feb 25, 2025
5c11932
refactor: 코드컨벤션 공백수정
dohy-eon Feb 25, 2025
be0d58f
feat: News 이미지 관련 파일 수정
dohy-eon Feb 26, 2025
73256de
refactor: 이미지 저장 로직 수정
dohy-eon Feb 26, 2025
ed8d2d2
refactor: 파일저장 로직수정
dohy-eon Feb 27, 2025
f1707fb
fix: 테스트케이스 수정
dohy-eon Feb 27, 2025
f61ebd1
fix: 파일저장 인코딩 수정
dohy-eon Feb 27, 2025
aa453ae
refactor: 응답 객체 추가 및 반환요청 수정
dohy-eon Feb 27, 2025
9ed50b7
refactor: NewsEntity-FileEntity 양방향 관계설정
dohy-eon Feb 27, 2025
564b27e
fix: 테스트코드 수정
dohy-eon Feb 27, 2025
b99ac23
fix: 테스트케이스 수정
dohy-eon Feb 27, 2025
f791528
infra: 파일 업로드 용량 제한 설정
ysw789 Feb 28, 2025
055941a
refactor: 파일 업로드, 조회 로직 리팩토링
ysw789 Feb 28, 2025
1a63c71
feat: 파일 업로드 관련 에러코드 추가
ysw789 Feb 28, 2025
6a89558
feat: 뉴스 CRUD 리팩토링
ysw789 Feb 28, 2025
5368fe0
Merge branch 'dev' into refactor/#47
ysw789 Feb 28, 2025
d680eac
chore: 테스트 케이스 임시 주석 처리
ysw789 Feb 28, 2025
649c94d
Merge remote-tracking branch 'origin/refactor/#47' into refactor/#47
ysw789 Feb 28, 2025
043fe4b
feat: fileType, targetId로 파일 조회 구현
ysw789 Mar 1, 2025
9e2cfad
feat: 뉴스 목록 썸네일 포함 응답 구현
ysw789 Mar 1, 2025
4ad696c
feat: 뉴스 수정 요청 이미지 삭제 요청 처리 구현
ysw789 Mar 1, 2025
6cb9259
chore: 미사용 파일 제거, 공백 수정
ysw789 Mar 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ dependencies {
implementation 'com.google.apis:google-api-services-sheets:v4-rev516-1.23.0'
implementation 'com.google.auth:google-auth-library-oauth2-http:0.20.0'





implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum ErrorCode {
INVALID_INQUIRY_PERIOD(400, "C018", "조회 기간이 아닙니다."),
SHEET_WRITE_FAIL(400, "C019", "시트에 데이터를 쓰는데 실패하였습니다."),
SHEET_READ_FAIL(400, "C200", "시트에 데이터를 쓰는데 실패하였습니다."),
FILE_ENCODE_FAIL(400, "C028", "파일 인코딩에 실패하였습니다.")
;

private final int status;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,89 +1,55 @@
package dmu.dasom.api.domain.news.controller;

import dmu.dasom.api.domain.news.dto.NewsRequestDto;
import dmu.dasom.api.domain.news.dto.NewsResponseDto;
import dmu.dasom.api.domain.news.dto.*;
import dmu.dasom.api.domain.news.service.NewsService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import jakarta.validation.constraints.Min;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import java.util.List;

@Tag(name = "NEWS API", description = "다솜소식 API")
@Tag(name = "NEWS API", description = "뉴스 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/news")
public class NewsController {

private final NewsService newsService;

public NewsController(NewsService newsService) {
this.newsService = newsService;
}

// 전체 조회
@Operation(summary = "소식 조회", description = "리스트로 조회")
@ApiResponse(responseCode = "200", description = "정상 응답",
content = @Content(mediaType = "application/json"))
@Operation(summary = "전체 뉴스 조회 (썸네일 포함)")
@GetMapping
public ResponseEntity<List<NewsResponseDto>> getAllNews() {
List<NewsResponseDto> newsList = newsService.getAllNews();
return ResponseEntity.ok(newsList);
public ResponseEntity<List<NewsListResponseDto>> getAllNews() {
return ResponseEntity.ok(newsService.getAllNews());
}

// 개별 조회
@Operation(summary = "소식 상세 조회", description = "ID로 특정 소식을 조회")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "소식을 찾을 수 없음")
})
@Operation(summary = "뉴스 상세 조회")
@GetMapping("/{id}")
public ResponseEntity<NewsResponseDto> getNewsById(@PathVariable Long id) {
NewsResponseDto responseDto = newsService.getNewsById(id);
return ResponseEntity.ok(responseDto);
public ResponseEntity<NewsResponseDto> getNewsById(@PathVariable @Min(1) Long id) {
return ResponseEntity.ok(newsService.getNewsById(id));
}

// 생성
@Operation(summary = "소식 등록", description = "새로운 소식을 등록")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "생성 완료"),
@ApiResponse(responseCode = "400", description = "유효하지 않은 요청 데이터",
content = @Content(mediaType = "application/json",
examples = @ExampleObject(value = "{ \"errorCode\": \"E003\", \"errorMessage\": \"유효하지 않은 입력입니다.\" }")))
})
@Operation(summary = "뉴스 등록")
@PostMapping
public ResponseEntity<NewsResponseDto> createNews(@Valid @RequestBody NewsRequestDto requestDto) {
NewsResponseDto responseDto = newsService.createNews(requestDto);
return ResponseEntity.status(201).body(responseDto);
public ResponseEntity<NewsCreationResponseDto> createNews(@Valid @RequestBody NewsRequestDto requestDto) {
return ResponseEntity.status(201).body(newsService.createNews(requestDto));
}

// 수정
@Operation(summary = "소식 수정", description = "ID로 특정 소식을 수정")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "수정 성공"),
@ApiResponse(responseCode = "404", description = "소식을 찾을 수 없음")
})
@Operation(summary = "뉴스 수정")
@PutMapping("/{id}")
public ResponseEntity<NewsResponseDto> updateNews(@PathVariable Long id, @Valid @RequestBody NewsRequestDto requestDto) {
NewsResponseDto updatedNews = newsService.updateNews(id, requestDto);
return ResponseEntity.ok(updatedNews);
public ResponseEntity<NewsResponseDto> updateNews(@PathVariable @Min(1) Long id,
@Valid @RequestBody NewsUpdateRequestDto requestDto) {
return ResponseEntity.ok(newsService.updateNews(id, requestDto));
}

// 삭제
@Operation(summary = "소식 삭제", description = "ID로 특정 소식을 삭제")
@ApiResponses({
@ApiResponse(responseCode = "204", description = "삭제 성공"),
@ApiResponse(responseCode = "404", description = "소식을 찾을 수 없음")
})
@Operation(summary = "뉴스 삭제")
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteNews(@PathVariable Long id) {
newsService.deleteNews(id);
return ResponseEntity.noContent().build();
return ResponseEntity.ok().build();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dmu.dasom.api.domain.news.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "NewsCreationResponseDto", description = "뉴스 생성 응답 DTO")
public class NewsCreationResponseDto {

@NotNull
@Schema(description = "뉴스 ID", example = "1")
private Long id;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dmu.dasom.api.domain.news.dto;

import dmu.dasom.api.global.file.dto.FileResponseDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(name = "NewsListResponseDto", description = "뉴스 리스트 응답 DTO")
public class NewsListResponseDto {

@Schema(description = "소식 ID", example = "1")
private Long id;

@Schema(description = "뉴스 제목", example = "제목")
private String title;

@Schema(description = "작성일", example = "2025-02-14T12:00:00")
private LocalDateTime createdAt;

@Schema(description = "인코딩된 이미지", example = "asdf", nullable = true)
private FileResponseDto image;

}
19 changes: 12 additions & 7 deletions src/main/java/dmu/dasom/api/domain/news/dto/NewsRequestDto.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dmu.dasom.api.domain.news.dto;

import dmu.dasom.api.domain.news.entity.NewsEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
Expand All @@ -13,16 +14,20 @@
@Schema(name = "NewsRequestDto", description = "뉴스 생성 요청 DTO")
public class NewsRequestDto {

@NotBlank
@Size(max = 100)
@NotBlank(message = "뉴스 제목은 필수입니다.")
@Size(max = 100, message = "뉴스 제목은 최대 100자입니다.")
@Schema(description = "뉴스 제목", example = "새로운 뉴스 제목")
private String title;

@NotBlank
@NotBlank(message = "뉴스 내용은 필수입니다.")
@Schema(description = "뉴스 내용", example = "새로운 뉴스 내용")
private String content;

@Size(max = 255)
@Schema(description = "뉴스 이미지 URL", example = "http://example.com/image.jpg", nullable = true)
private String imageUrl;
}
public NewsEntity toEntity() {
return NewsEntity.builder()
.title(this.title)
.content(this.content)
.build();
}

}
23 changes: 12 additions & 11 deletions src/main/java/dmu/dasom/api/domain/news/dto/NewsResponseDto.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package dmu.dasom.api.domain.news.dto;

import dmu.dasom.api.global.file.dto.FileResponseDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(name = "NewsResponseDto", description = "뉴스 응답 DTO")
public class NewsResponseDto {

Expand All @@ -21,14 +29,7 @@ public class NewsResponseDto {
@Schema(description = "작성일", example = "2025-02-14T12:00:00")
private LocalDateTime createdAt;

@Schema(description = "뉴스 이미지 URL", example = "http://example.com/image.jpg", nullable = true)
private String imageUrl;

public NewsResponseDto(Long id, String title, String content, LocalDateTime createdAt, String imageUrl) {
this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.imageUrl = imageUrl;
}
}
@Schema(description = "인코딩된 이미지", example = "asdf", nullable = true)
private List<FileResponseDto> images;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dmu.dasom.api.domain.news.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "NewsUpdateRequestDto", description = "뉴스 수정 요청 DTO")
public class NewsUpdateRequestDto {

@Size(max = 100, message = "뉴스 제목은 최대 100자입니다.")
@Schema(description = "수정할 뉴스 제목", example = "뉴스 제목", nullable = true)
private String title;

@Schema(description = "수정할 뉴스 내용", example = "뉴스 내용", nullable = true)
private String content;

@Schema(description = "삭제할 이미지 ID 목록", example = "[1, 2, 3]", nullable = true)
private List<Long> deleteImageIds;

}
63 changes: 25 additions & 38 deletions src/main/java/dmu/dasom/api/domain/news/entity/NewsEntity.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package dmu.dasom.api.domain.news.entity;

import dmu.dasom.api.domain.common.BaseEntity;
import dmu.dasom.api.domain.common.Status;
import dmu.dasom.api.domain.news.dto.NewsListResponseDto;
import dmu.dasom.api.domain.news.dto.NewsResponseDto;
import dmu.dasom.api.global.file.dto.FileResponseDto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.*;
import org.apache.commons.lang3.ObjectUtils;
import org.hibernate.annotations.DynamicUpdate;

import java.util.List;

@DynamicUpdate
@Getter
@Entity
@Table(name = "news")
Expand All @@ -21,53 +24,37 @@ public class NewsEntity extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "뉴스 ID", example = "1")
private Long id;

@NotNull
@Schema(description = "뉴스 제목", example = "뉴스 예제 제목")
@Column(nullable = false, length = 100)
private String title;

@Lob
@NotNull
@Schema(description = "뉴스 내용", example = "뉴스 예제 내용")
@Column(nullable = false)
private String content;


@Schema(description = "뉴스 이미지 URL", example = "http://example.com/image.jpg")
@Column(length = 255)
private String imageUrl;

// 뉴스 상태 업데이트
public void updateStatus(Status status) {
super.updateStatus(status);
public void update(String title, String content) {
this.title = title;
this.content = content;
}

// NewsEntity → NewsResponseDto 변환
public NewsResponseDto toResponseDto() {
return new NewsResponseDto(id, title, content, getCreatedAt(), imageUrl);
public NewsResponseDto toResponseDto(List<FileResponseDto> images) {
return NewsResponseDto.builder()
.id(this.id)
.title(this.title)
.content(this.content)
.createdAt(getCreatedAt())
.images(ObjectUtils.isEmpty(images) ? null : images)
.build();
}

//수정기능
public void update(String title, String content, String imageUrl) {
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("제목은 필수입니다");
}
if (title.length() > 100) {
throw new IllegalArgumentException("제목은 100자까지");
}
if (content == null || content.isBlank()) {
throw new IllegalArgumentException("내용은 필수입니다");
}
if (imageUrl != null && imageUrl.length() > 255) {
throw new IllegalArgumentException("이미지 URL은 255자까지");
}

this.title = title;
this.content = content;
this.imageUrl = imageUrl;
public NewsListResponseDto toListResponseDto(FileResponseDto file) {
return NewsListResponseDto.builder()
.id(this.id)
.title(this.title)
.createdAt(getCreatedAt())
.image(file)
.build();
}

}
Loading