From fced3bf5f298c706a78724589b4933345dd29259 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 21:33:58 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[REFACTOR]=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../movelog/domain/record/domain/Keyword.java | 7 ++- .../movelog/domain/record/domain/Record.java | 6 +-- .../record/presentation/RecordController.java | 3 +- .../domain/record/service/RecordService.java | 52 ++++++++++++------- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/movelog/domain/record/domain/Keyword.java b/src/main/java/com/movelog/domain/record/domain/Keyword.java index 494e60f..82a15d2 100644 --- a/src/main/java/com/movelog/domain/record/domain/Keyword.java +++ b/src/main/java/com/movelog/domain/record/domain/Keyword.java @@ -22,11 +22,16 @@ public class Keyword extends BaseEntity { private String keyword; + @Enumerated(EnumType.STRING) // Enum을 문자열로 저장 + @Column(name = "verb_type") + private VerbType verbType; + @OneToMany(mappedBy = "keyword") private List records = new ArrayList<>(); @Builder - public Keyword(String keyword) { + public Keyword(String keyword, VerbType verbType) { this.keyword = keyword; + this.verbType = verbType; } } diff --git a/src/main/java/com/movelog/domain/record/domain/Record.java b/src/main/java/com/movelog/domain/record/domain/Record.java index eb801f1..b577d82 100644 --- a/src/main/java/com/movelog/domain/record/domain/Record.java +++ b/src/main/java/com/movelog/domain/record/domain/Record.java @@ -28,9 +28,6 @@ public class Record extends BaseEntity { @JoinColumn(name = "keyword_id") private Keyword keyword; - @Enumerated(EnumType.STRING) // Enum을 문자열로 저장 - private VerbType verbType; - @Column(name = "record_image") private String recordImage; @@ -38,10 +35,9 @@ public class Record extends BaseEntity { private java.time.LocalDateTime actionTime; @Builder - public Record(User user, Keyword keyword, VerbType verbType, String recordImage) { + public Record(User user, Keyword keyword, String recordImage) { this.user = user; this.keyword = keyword; - this.verbType = verbType; this.recordImage = recordImage; this.actionTime = actionTime == null? LocalDateTime.now():actionTime; } diff --git a/src/main/java/com/movelog/domain/record/presentation/RecordController.java b/src/main/java/com/movelog/domain/record/presentation/RecordController.java index 236ada2..227ff39 100644 --- a/src/main/java/com/movelog/domain/record/presentation/RecordController.java +++ b/src/main/java/com/movelog/domain/record/presentation/RecordController.java @@ -2,6 +2,7 @@ import com.movelog.domain.record.dto.request.CreateRecordReq; import com.movelog.domain.record.service.RecordService; +import com.movelog.global.config.security.token.CurrentUser; import com.movelog.global.config.security.token.UserPrincipal; import com.movelog.global.payload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -39,7 +40,7 @@ public ResponseEntity retrieveTodayRecord( ApiResponse result = ApiResponse.builder() .check(true) - .information(recordService.retrieveTodayRecord(userPrincipal.getId())) + .information(recordService.retrieveTodayRecord(5L)) .build(); return ResponseEntity.ok(result); } diff --git a/src/main/java/com/movelog/domain/record/service/RecordService.java b/src/main/java/com/movelog/domain/record/service/RecordService.java index ab5e3f3..de6b003 100644 --- a/src/main/java/com/movelog/domain/record/service/RecordService.java +++ b/src/main/java/com/movelog/domain/record/service/RecordService.java @@ -8,7 +8,6 @@ import com.movelog.domain.record.repository.RecordRepository; import com.movelog.domain.user.domain.User; import com.movelog.domain.user.domain.repository.UserRepository; -import com.movelog.global.DefaultAssert; import com.movelog.global.util.S3Util; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,8 +18,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; @Service @@ -36,22 +34,24 @@ public class RecordService { @Transactional public void createRecord(Long userId, CreateRecordReq createRecordReq, MultipartFile img) { User user = validUserById(userId); + // User user = validUserById(5L); String recordImgUrl = s3Util.upload(img); log.info("recordImgUrl: {}", recordImgUrl); - Keyword keyword = Keyword.builder() - .keyword(createRecordReq.getNoun()) - .build(); - - keywordRepository.save(keyword); String verb = createRecordReq.getVerbType(); try { VerbType verbType = VerbType.fromValue(verb); + Keyword keyword = Keyword.builder() + .keyword(createRecordReq.getNoun()) + .verbType(verbType) + .build(); + + keywordRepository.save(keyword); + Record record = Record.builder() .user(user) .keyword(keyword) - .verbType(verbType) .recordImage(recordImgUrl) // .actionTime(LocalDateTime.now()) .build(); @@ -62,26 +62,40 @@ public void createRecord(Long userId, CreateRecordReq createRecordReq, Multipart } } + private User validUserById(Long userId) { Optional userOptional = userRepository.findById(userId); return userOptional.get(); } - public List retrieveTodayRecord(Long userId) { + public Map retrieveTodayRecord(Long userId) { User user = validUserById(userId); - // 현재 날짜 가져오기 - LocalDate today = LocalDate.now(); // 오늘 날짜 (2025-01-05 기준) // 오늘의 시작 시간과 끝 시간 계산 - LocalDateTime startOfDay = today.atStartOfDay(); // 2025-01-05T00:00:00 - LocalDateTime endOfDay = today.atTime(LocalTime.MAX); // 2025-01-05T23:59:59.999999999 + LocalDate today = LocalDate.now(); + LocalDateTime startOfDay = today.atStartOfDay(); + LocalDateTime endOfDay = today.atTime(LocalTime.MAX); - // 레코드 조회 + // 오늘 기록된 레코드 가져오기 List records = recordRepository.findByUserAndActionTimeBetween(user, startOfDay, endOfDay); - return records.stream() - .map(Record::getVerbType) // Record 객체에서 verbType 추출 - .distinct() // 중복 제거 - .collect(Collectors.toList()); + log.info("Retrieved Records: {}", records); + + // Keyword에서 VerbType 추출 + Set todayVerbTypes = records.stream() + .map(Record::getKeyword) // Record -> Keyword + .map(Keyword::getVerbType) // Keyword -> VerbType + .collect(Collectors.toSet()); // 중복 제거 + + log.info("Today VerbTypes: {}", todayVerbTypes); + + // 모든 VerbType에 대해 존재 여부를 반환 + return Arrays.stream(VerbType.values()) + .collect(Collectors.toMap( + VerbType::getVerbType, // 키: VerbType의 문자열 값 + todayVerbTypes::contains // 값: 오늘 VerbType에 포함 여부 + )); } + + } From aa9c46fa08237e5b4ae68aa3a8259a3d43026ca5 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 21:34:23 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[FEAT]=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../record/exception/KeywordNotFoundException.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/movelog/domain/record/exception/KeywordNotFoundException.java diff --git a/src/main/java/com/movelog/domain/record/exception/KeywordNotFoundException.java b/src/main/java/com/movelog/domain/record/exception/KeywordNotFoundException.java new file mode 100644 index 0000000..4617f22 --- /dev/null +++ b/src/main/java/com/movelog/domain/record/exception/KeywordNotFoundException.java @@ -0,0 +1,11 @@ +package com.movelog.domain.record.exception; + + +import com.movelog.global.exception.NotFoundException; + +public class KeywordNotFoundException extends NotFoundException { + + public KeywordNotFoundException() { + super("U002", "키워드를 찾을 수 없습니다."); + } +} From 8367cd2ee8c1a5617b440a7945e63c5d83d518c1 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 21:34:47 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[REFACTOR]=20=ED=97=A4=EB=93=9C=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=83=9D=EC=84=B1=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...dlineGeneratorService.java => HeadLineGeneratorService.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/com/movelog/domain/news/application/{HeadlineGeneratorService.java => HeadLineGeneratorService.java} (99%) diff --git a/src/main/java/com/movelog/domain/news/application/HeadlineGeneratorService.java b/src/main/java/com/movelog/domain/news/application/HeadLineGeneratorService.java similarity index 99% rename from src/main/java/com/movelog/domain/news/application/HeadlineGeneratorService.java rename to src/main/java/com/movelog/domain/news/application/HeadLineGeneratorService.java index ad7493f..9d4af06 100644 --- a/src/main/java/com/movelog/domain/news/application/HeadlineGeneratorService.java +++ b/src/main/java/com/movelog/domain/news/application/HeadLineGeneratorService.java @@ -14,7 +14,7 @@ @Service @RequiredArgsConstructor @Slf4j -public class HeadlineGeneratorService { +public class HeadLineGeneratorService { private final GptService gptService; From 100f54018a67f8ec4254f4833ead4910262824e7 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 23:06:33 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[FIX]=20=ED=97=A4=EB=93=9C=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=ED=82=A4=EC=9B=8C?= =?UTF-8?q?=EB=93=9CId=EB=A5=BC=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=9B=EC=9D=8C=EC=9C=BC=EB=A1=9C=EC=8D=A8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../movelog/domain/news/dto/request/NewsHeadLineReq.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java b/src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java index 0c332e8..7076a49 100644 --- a/src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java +++ b/src/main/java/com/movelog/domain/news/dto/request/NewsHeadLineReq.java @@ -13,12 +13,4 @@ public class NewsHeadLineReq { @Schema( type = "String", example ="첫 도전, 오랜만에 다시, 꾸준히 이어온 기록, 끊어낸 습관 중 택 1", description="뉴스 헤드라인 고정 옵션입니다.") private String option; - - @Schema( type = "String", example ="했어요, 먹었어요, 갔어요 중 택 1", description="사용자가 선택한 동사 정보입니다.") - private String verb; - - @Schema( type = "String", example ="클라이밍", description="사용자가 선택한 명사 정보입니다.") - private String noun; - - } From 026f59731546eccea9157d236945ac08119594f7 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 23:47:23 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[REFACTOR]=20=EC=9D=91=EB=8B=B5=EA=B0=92?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A4=91=EB=B3=B5=20=EC=B5=9C=EC=86=8C=ED=99=94?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20ApiResponse=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EC=9C=A0=ED=8B=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../movelog/global/payload/ApiResponse.java | 23 ++++++++------ .../movelog/global/util/ApiResponseUtil.java | 31 +++++++++++++++++++ .../global/util/ErrorResponseUtil.java | 22 +++++++++++++ 3 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/movelog/global/util/ApiResponseUtil.java create mode 100644 src/main/java/com/movelog/global/util/ErrorResponseUtil.java diff --git a/src/main/java/com/movelog/global/payload/ApiResponse.java b/src/main/java/com/movelog/global/payload/ApiResponse.java index 8b563b2..b215f2f 100644 --- a/src/main/java/com/movelog/global/payload/ApiResponse.java +++ b/src/main/java/com/movelog/global/payload/ApiResponse.java @@ -1,24 +1,29 @@ package com.movelog.global.payload; - import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Data; @Data -public class ApiResponse { - - @Schema( type = "boolean", example = "true", description="올바르게 로직을 처리했으면 True, 아니면 False를 반환합니다.") - private boolean check; +public class ApiResponse { - @Schema( type = "object", example = "information", description="restful의 정보를 감싸 표현합니다. object형식으로 표현합니다.") - private Object information; + @Schema(type = "boolean", example = "true", description = "로직 처리 성공 여부를 반환합니다.") + private final boolean check; - public ApiResponse(){}; + @Schema(type = "object", description = "응답 데이터를 담는 필드입니다.") + private final T information; @Builder - public ApiResponse(boolean check, Object information) { + public ApiResponse(boolean check, T information) { this.check = check; this.information = information; } + // 정적 팩토리 메서드로 명확한 응답 생성 + public static ApiResponse success(T data) { + return new ApiResponse<>(true, data); + } + + public static ApiResponse failure(T errorMessage) { + return new ApiResponse<>(false, errorMessage); + } } diff --git a/src/main/java/com/movelog/global/util/ApiResponseUtil.java b/src/main/java/com/movelog/global/util/ApiResponseUtil.java new file mode 100644 index 0000000..53e4105 --- /dev/null +++ b/src/main/java/com/movelog/global/util/ApiResponseUtil.java @@ -0,0 +1,31 @@ +package com.movelog.global.util; + +import com.movelog.global.payload.ApiResponse; +import com.movelog.global.payload.ErrorResponse; +import lombok.experimental.UtilityClass; +import org.springframework.http.ResponseEntity; + +import java.util.List; +@UtilityClass +public class ApiResponseUtil { + + public static ResponseEntity> success(T data) { + ApiResponse apiResponse = ApiResponse.builder() + .check(true) + .information(data) + .build(); + + return ResponseEntity.ok(apiResponse); + } + + public static ResponseEntity>> success(List data) { + ApiResponse> apiResponse = ApiResponse.>builder() + .check(true) + .information(data) + .build(); + + return ResponseEntity.ok(apiResponse); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/movelog/global/util/ErrorResponseUtil.java b/src/main/java/com/movelog/global/util/ErrorResponseUtil.java new file mode 100644 index 0000000..d5cd410 --- /dev/null +++ b/src/main/java/com/movelog/global/util/ErrorResponseUtil.java @@ -0,0 +1,22 @@ +package com.movelog.global.util; + +import com.movelog.global.payload.ErrorResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class ErrorResponseUtil { + + public static ResponseEntity failure( + String errorCode, HttpStatus status, String message, Class clazz) { + ErrorResponse errorResponse = ErrorResponse.builder() + .code(errorCode) + .status(status.value()) + .message(message) + .clazz(clazz.getSimpleName()) + .build(); + + return ResponseEntity.status(status).body(errorResponse); + } + +} + From b73603d070fe3c03df0222051596cddc015650b0 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 23:49:17 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[REFACTOR]=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EA=B8=B0=EB=A1=9D=20=EA=B4=80=EB=A0=A8=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/movelog/domain/news/domain/News.java | 38 +++++++++++++++++++ .../domain/repository/NewsRepository.java | 9 +++++ .../movelog/domain/record/domain/Keyword.java | 14 ++++++- .../movelog/domain/record/domain/Record.java | 7 +--- .../record/presentation/RecordController.java | 1 - .../record/repository/KeywordRepository.java | 4 ++ .../record/repository/RecordRepository.java | 3 +- .../domain/record/service/RecordService.java | 13 +++++-- .../com/movelog/domain/user/domain/User.java | 3 +- 9 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/movelog/domain/news/domain/News.java create mode 100644 src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java diff --git a/src/main/java/com/movelog/domain/news/domain/News.java b/src/main/java/com/movelog/domain/news/domain/News.java new file mode 100644 index 0000000..3f254e3 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/domain/News.java @@ -0,0 +1,38 @@ +package com.movelog.domain.news.domain; + +import com.movelog.domain.common.BaseEntity; +import com.movelog.domain.record.domain.Keyword; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "News") +@NoArgsConstructor +@Getter +public class News extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "news_id", updatable = false) + private Long newsId; + + private String headLine; //뉴스 헤드라인 + + private String newsUrl; //뉴스 URL + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "keyword_id") + private Keyword keyword; + + @Builder + public News(String headLine, String newsUrl, Keyword keyword) { + this.headLine = headLine; + this.newsUrl = newsUrl; + this.keyword = keyword; + } + + + +} diff --git a/src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java b/src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java new file mode 100644 index 0000000..644fcf0 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/domain/repository/NewsRepository.java @@ -0,0 +1,9 @@ +package com.movelog.domain.news.domain.repository; + +import com.movelog.domain.news.domain.News; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface NewsRepository extends JpaRepository { +} diff --git a/src/main/java/com/movelog/domain/record/domain/Keyword.java b/src/main/java/com/movelog/domain/record/domain/Keyword.java index 82a15d2..31ee192 100644 --- a/src/main/java/com/movelog/domain/record/domain/Keyword.java +++ b/src/main/java/com/movelog/domain/record/domain/Keyword.java @@ -1,6 +1,8 @@ package com.movelog.domain.record.domain; import com.movelog.domain.common.BaseEntity; +import com.movelog.domain.news.domain.News; +import com.movelog.domain.user.domain.User; import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; @@ -20,17 +22,25 @@ public class Keyword extends BaseEntity { @Column(name = "keyword_id", updatable = false) private Long keywordId; - private String keyword; + private String keyword; //명사 @Enumerated(EnumType.STRING) // Enum을 문자열로 저장 @Column(name = "verb_type") private VerbType verbType; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + @OneToMany(mappedBy = "keyword") private List records = new ArrayList<>(); + @OneToMany(mappedBy = "keyword") + private List news = new ArrayList<>(); + @Builder - public Keyword(String keyword, VerbType verbType) { + public Keyword(User user, String keyword, VerbType verbType) { + this.user = user; this.keyword = keyword; this.verbType = verbType; } diff --git a/src/main/java/com/movelog/domain/record/domain/Record.java b/src/main/java/com/movelog/domain/record/domain/Record.java index b577d82..9538827 100644 --- a/src/main/java/com/movelog/domain/record/domain/Record.java +++ b/src/main/java/com/movelog/domain/record/domain/Record.java @@ -20,10 +20,6 @@ public class Record extends BaseEntity { @Column(name = "record_id", updatable = false) private Long recordId; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "keyword_id") private Keyword keyword; @@ -35,8 +31,7 @@ public class Record extends BaseEntity { private java.time.LocalDateTime actionTime; @Builder - public Record(User user, Keyword keyword, String recordImage) { - this.user = user; + public Record(Keyword keyword, String recordImage) { this.keyword = keyword; this.recordImage = recordImage; this.actionTime = actionTime == null? LocalDateTime.now():actionTime; diff --git a/src/main/java/com/movelog/domain/record/presentation/RecordController.java b/src/main/java/com/movelog/domain/record/presentation/RecordController.java index 227ff39..b73c872 100644 --- a/src/main/java/com/movelog/domain/record/presentation/RecordController.java +++ b/src/main/java/com/movelog/domain/record/presentation/RecordController.java @@ -2,7 +2,6 @@ import com.movelog.domain.record.dto.request.CreateRecordReq; import com.movelog.domain.record.service.RecordService; -import com.movelog.global.config.security.token.CurrentUser; import com.movelog.global.config.security.token.UserPrincipal; import com.movelog.global.payload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; diff --git a/src/main/java/com/movelog/domain/record/repository/KeywordRepository.java b/src/main/java/com/movelog/domain/record/repository/KeywordRepository.java index 5a5d7bd..d4f5e6c 100644 --- a/src/main/java/com/movelog/domain/record/repository/KeywordRepository.java +++ b/src/main/java/com/movelog/domain/record/repository/KeywordRepository.java @@ -2,9 +2,13 @@ import com.movelog.domain.record.domain.Keyword; import com.movelog.domain.record.domain.Record; +import com.movelog.domain.user.domain.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface KeywordRepository extends JpaRepository { + List findByUser(User user); } diff --git a/src/main/java/com/movelog/domain/record/repository/RecordRepository.java b/src/main/java/com/movelog/domain/record/repository/RecordRepository.java index 06ddd46..3482844 100644 --- a/src/main/java/com/movelog/domain/record/repository/RecordRepository.java +++ b/src/main/java/com/movelog/domain/record/repository/RecordRepository.java @@ -1,5 +1,6 @@ package com.movelog.domain.record.repository; +import com.movelog.domain.record.domain.Keyword; import com.movelog.domain.record.domain.Record; import com.movelog.domain.user.domain.User; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,5 +11,5 @@ @Repository public interface RecordRepository extends JpaRepository { - List findByUserAndActionTimeBetween(User user, LocalDateTime startOfDay, LocalDateTime endOfDay); + List findByKeywordInAndActionTimeBetween(List keywords, LocalDateTime startTime, LocalDateTime endTime); } diff --git a/src/main/java/com/movelog/domain/record/service/RecordService.java b/src/main/java/com/movelog/domain/record/service/RecordService.java index de6b003..9b4dc1e 100644 --- a/src/main/java/com/movelog/domain/record/service/RecordService.java +++ b/src/main/java/com/movelog/domain/record/service/RecordService.java @@ -35,7 +35,7 @@ public class RecordService { public void createRecord(Long userId, CreateRecordReq createRecordReq, MultipartFile img) { User user = validUserById(userId); // User user = validUserById(5L); - String recordImgUrl = s3Util.upload(img); + String recordImgUrl = s3Util.uploadToRecordFolder(img); log.info("recordImgUrl: {}", recordImgUrl); String verb = createRecordReq.getVerbType(); @@ -43,6 +43,7 @@ public void createRecord(Long userId, CreateRecordReq createRecordReq, Multipart VerbType verbType = VerbType.fromValue(verb); Keyword keyword = Keyword.builder() + .user(user) .keyword(createRecordReq.getNoun()) .verbType(verbType) .build(); @@ -50,7 +51,6 @@ public void createRecord(Long userId, CreateRecordReq createRecordReq, Multipart keywordRepository.save(keyword); Record record = Record.builder() - .user(user) .keyword(keyword) .recordImage(recordImgUrl) // .actionTime(LocalDateTime.now()) @@ -69,6 +69,7 @@ private User validUserById(Long userId) { } public Map retrieveTodayRecord(Long userId) { + // 유저 유효성 검사 및 조회 User user = validUserById(userId); // 오늘의 시작 시간과 끝 시간 계산 @@ -76,8 +77,11 @@ public Map retrieveTodayRecord(Long userId) { LocalDateTime startOfDay = today.atStartOfDay(); LocalDateTime endOfDay = today.atTime(LocalTime.MAX); - // 오늘 기록된 레코드 가져오기 - List records = recordRepository.findByUserAndActionTimeBetween(user, startOfDay, endOfDay); + // 유저가 소유한 키워드 가져오기 + List keywords = keywordRepository.findByUser(user); + + // 키워드에 연결된 기록 중 오늘 생성된 기록 가져오기 + List records = recordRepository.findByKeywordInAndActionTimeBetween(keywords, startOfDay, endOfDay); log.info("Retrieved Records: {}", records); @@ -98,4 +102,5 @@ public Map retrieveTodayRecord(Long userId) { } + } diff --git a/src/main/java/com/movelog/domain/user/domain/User.java b/src/main/java/com/movelog/domain/user/domain/User.java index b43f04f..81c7811 100644 --- a/src/main/java/com/movelog/domain/user/domain/User.java +++ b/src/main/java/com/movelog/domain/user/domain/User.java @@ -3,6 +3,7 @@ import com.movelog.domain.common.BaseEntity; +import com.movelog.domain.record.domain.Keyword; import com.movelog.domain.record.domain.Record; import jakarta.persistence.*; import lombok.AccessLevel; @@ -40,7 +41,7 @@ public class User extends BaseEntity { private String fcmToken; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List records = new ArrayList<>(); + private List keywords = new ArrayList<>(); @Builder From 9ea1f2639628ad60b0bf4c25137083c2b7dbabd0 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 23:49:56 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[REFACTOR]=20enum=20String=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/movelog/domain/record/domain/VerbType.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/movelog/domain/record/domain/VerbType.java b/src/main/java/com/movelog/domain/record/domain/VerbType.java index 16a2e20..d38c4cf 100644 --- a/src/main/java/com/movelog/domain/record/domain/VerbType.java +++ b/src/main/java/com/movelog/domain/record/domain/VerbType.java @@ -22,4 +22,9 @@ public static VerbType fromValue(String value) { } throw new IllegalArgumentException("No enum constant for value: " + value); } + + public static String getStringVerbType(VerbType verbType) { + return verbType.getVerbType(); + } + } From bd46e13621dd71e027db16bbce163aba48502fcd Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 23:50:30 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[FEAT]=20S3=20=EB=B2=84=ED=82=B7=20?= =?UTF-8?q?=EB=82=B4=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=9C=A0=ED=8B=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/movelog/global/util/S3Util.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/movelog/global/util/S3Util.java b/src/main/java/com/movelog/global/util/S3Util.java index 79df667..ea4e645 100644 --- a/src/main/java/com/movelog/global/util/S3Util.java +++ b/src/main/java/com/movelog/global/util/S3Util.java @@ -48,23 +48,37 @@ public AmazonS3Client amazonS3Client() { .build(); } - public String upload(MultipartFile file) { + + public String uploadToRecordFolder(MultipartFile file) { + return uploadToFolder(file, "record"); + } + + public String uploadToNewsFolder(MultipartFile file) { + return uploadToFolder(file, "news"); + } + + private String uploadToFolder(MultipartFile file, String folder) { + if (!folder.endsWith("/")) { + folder += "/"; + } + return upload(file, folder + createFileName(file.getOriginalFilename())); + } + + private String upload(MultipartFile file, String filePath) { String imageUrl = ""; - String fileName = createFileName(file.getOriginalFilename()); ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentLength(file.getSize()); objectMetadata.setContentType(file.getContentType()); try (InputStream inputStream = file.getInputStream()) { - s3Client.putObject(new PutObjectRequest(bucket, fileName, inputStream, objectMetadata) + s3Client.putObject(new PutObjectRequest(bucket, filePath, inputStream, objectMetadata) .withCannedAcl(CannedAccessControlList.PublicRead)); - imageUrl = s3Client.getUrl(bucket, fileName).toString(); + imageUrl = s3Client.getUrl(bucket, filePath).toString(); } catch (IOException e) { throw new IllegalArgumentException("IMAGE_UPLOAD_ERROR"); } return imageUrl; } - // 이미지파일명 중복 방지 private String createFileName(String fileName) { return UUID.randomUUID().toString().concat(getFileExtension(fileName)); From b242caabe3efc15f81598f57379b7337b3d82695 Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 23:50:41 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[FEAT]=20=EB=89=B4=EC=8A=A4=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/news/application/NewsService.java | 59 ++++++++++++++++--- .../news/dto/request/CreateNewsReq.java | 18 ++++++ .../news/presentation/NewsController.java | 53 +++++++++++++---- 3 files changed, 111 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/movelog/domain/news/dto/request/CreateNewsReq.java diff --git a/src/main/java/com/movelog/domain/news/application/NewsService.java b/src/main/java/com/movelog/domain/news/application/NewsService.java index e782e45..3233444 100644 --- a/src/main/java/com/movelog/domain/news/application/NewsService.java +++ b/src/main/java/com/movelog/domain/news/application/NewsService.java @@ -1,17 +1,24 @@ package com.movelog.domain.news.application; +import com.movelog.domain.news.domain.News; +import com.movelog.domain.news.domain.repository.NewsRepository; +import com.movelog.domain.news.dto.request.CreateNewsReq; import com.movelog.domain.news.dto.request.NewsHeadLineReq; import com.movelog.domain.news.dto.response.HeadLineRes; +import com.movelog.domain.record.domain.Keyword; +import com.movelog.domain.record.domain.VerbType; +import com.movelog.domain.record.exception.KeywordNotFoundException; +import com.movelog.domain.record.repository.KeywordRepository; import com.movelog.domain.user.application.UserService; import com.movelog.domain.user.domain.User; import com.movelog.domain.user.domain.repository.UserRepository; import com.movelog.domain.user.exception.UserNotFoundException; -import com.movelog.global.DefaultAssert; import com.movelog.global.config.security.token.UserPrincipal; +import com.movelog.global.util.S3Util; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Optional; @@ -20,24 +27,58 @@ @RequiredArgsConstructor @Transactional(readOnly = true) public class NewsService { - private final HeadlineGeneratorService headlineGeneratorService; + private final HeadLineGeneratorService headLineGeneratorService; private final UserService userService; - private final UserRepository userRepository; + private final KeywordRepository keywordRepository; + private final NewsRepository newsRepository; + private final S3Util s3Util; - public List createHeadLine(UserPrincipal userPrincipal, NewsHeadLineReq newsHeadLineReq) { + public List createHeadLine(UserPrincipal userPrincipal, Long keywordId, NewsHeadLineReq newsHeadLineReq) { User user = validateUser(userPrincipal); // id가 5인 유저 정보(테스트용) // User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new); + Keyword keyword = validateKeyword(keywordId); String option = newsHeadLineReq.getOption(); - String verb = newsHeadLineReq.getVerb(); - String noun = newsHeadLineReq.getNoun(); - return headlineGeneratorService.generateHeadLine(option, verb, noun); + String verb = VerbType.getStringVerbType(keyword.getVerbType()); + String noun = keyword.getKeyword(); + + return headLineGeneratorService.generateHeadLine(option, verb, noun); } + @Transactional + public void createNews(UserPrincipal userPrincipal, Long keywordId, CreateNewsReq createNewsReq, MultipartFile img) { + User user = validateUser(userPrincipal); + // id가 5인 유저 정보(테스트용) + // User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new); + Keyword keyword = validateKeyword(keywordId); + + String newsImgUrl = s3Util.uploadToNewsFolder(img); + + News news = News.builder() + .headLine(createNewsReq.getHeadLine()) + .newsUrl(newsImgUrl) + .keyword(keyword) + .build(); + newsRepository.save(news); + + } + + + + // User 정보 검증 private User validateUser(UserPrincipal userPrincipal) { Optional userOptional = userService.findById(userPrincipal.getId()); - DefaultAssert.isOptionalPresent(userOptional); + if (userOptional.isEmpty()) { throw new UserNotFoundException(); } return userOptional.get(); } + + // Keyword 정보 검증 + private Keyword validateKeyword(Long keywordId) { + Optional keywordOptional = keywordRepository.findById(keywordId); + if (keywordOptional.isEmpty()) { throw new KeywordNotFoundException(); } + return keywordOptional.get(); + } + + } diff --git a/src/main/java/com/movelog/domain/news/dto/request/CreateNewsReq.java b/src/main/java/com/movelog/domain/news/dto/request/CreateNewsReq.java new file mode 100644 index 0000000..14940f4 --- /dev/null +++ b/src/main/java/com/movelog/domain/news/dto/request/CreateNewsReq.java @@ -0,0 +1,18 @@ +package com.movelog.domain.news.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class CreateNewsReq { + + @Schema( type = "String", example ="5년 만의 첫 도전, 무엇이 그를 움직이게 했나?", description="사용자가 선택한/생성한 헤드라인 정보입니다.") + private String headLine; + +} diff --git a/src/main/java/com/movelog/domain/news/presentation/NewsController.java b/src/main/java/com/movelog/domain/news/presentation/NewsController.java index 06ffa67..3b0e599 100644 --- a/src/main/java/com/movelog/domain/news/presentation/NewsController.java +++ b/src/main/java/com/movelog/domain/news/presentation/NewsController.java @@ -1,21 +1,28 @@ package com.movelog.domain.news.presentation; import com.movelog.domain.news.application.NewsService; +import com.movelog.domain.news.dto.request.CreateNewsReq; import com.movelog.domain.news.dto.request.NewsHeadLineReq; import com.movelog.domain.news.dto.response.HeadLineRes; import com.movelog.global.config.security.token.CurrentUser; import com.movelog.global.config.security.token.UserPrincipal; +import com.movelog.global.payload.Message; +import com.movelog.global.util.ApiResponseUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; + import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.ErrorResponse; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -30,17 +37,43 @@ public class NewsController { @Operation(summary = "뉴스 헤드라인 생성 API", description = "뉴스 헤드라인을 생성하는 API입니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "뉴스 헤드라인 생성 성공"), - @ApiResponse(responseCode = "400", description = "뉴스 헤드라인 생성 실패") + @ApiResponse(responseCode = "200", description = "뉴스 헤드라인 생성 성공", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = List.class))), + @ApiResponse(responseCode = "400", description = "뉴스 헤드라인 생성 실패", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) }) - @PostMapping("/headline") - public List createHeadLine( - @Parameter(description = "Access Token을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal, + @PostMapping("/{keywordId}/headline") + public ResponseEntity createHeadLine( + @Parameter(description = "Access Token을 입력해주세요.", required = true) @AuthenticationPrincipal UserPrincipal userPrincipal, + @Parameter(description = "키워드 ID(동사-명사 쌍에 대한 ID)를 입력해주세요.", required = true) @PathVariable Long keywordId, @Parameter(description = "뉴스 헤드라인 생성 요청", required = true) @RequestBody NewsHeadLineReq newsHeadLineReq ) { - return newsService.createHeadLine(userPrincipal, newsHeadLineReq); + List response = newsService.createHeadLine(userPrincipal, keywordId, newsHeadLineReq); + return ResponseEntity.ok(ApiResponseUtil.success(response)); } + @Operation(summary = "뉴스 생성 및 저장 API(기존 이미지 기록 기반)", description = "사용자의 기존 기록 이미지로 뉴스를 생성합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "뉴스 생성 및 저장 성공", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Message.class))), + @ApiResponse(responseCode = "400", description = "뉴스 생성 및 저장 실패", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + @PostMapping("/{keywordId}") + public ResponseEntity createNews( + @Parameter(description = "Access Token을 입력해주세요.", required = true) @AuthenticationPrincipal UserPrincipal userPrincipal, + @Parameter(description = "키워드 ID(동사-명사 쌍에 대한 ID)를 입력해주세요.", required = true) @PathVariable Long keywordId, + @Parameter(description = "뉴스 생성 및 저장을 위한 정보를 입력해주세요", required = true) @RequestPart CreateNewsReq createNewsReq, + @Parameter(description = "뉴스 이미지를 파일 형식으로 입력해주세요", required = true) @RequestParam(value = "img", required = false) MultipartFile img + ) { + newsService.createNews(userPrincipal, keywordId, createNewsReq, img); + return ResponseEntity.ok(ApiResponseUtil.success(Message.builder().message("뉴스가 생성되었습니다.").build())); + } + + + + + } From b855b686193936b2e9aeb71f94ff0d8c5ea2d51b Mon Sep 17 00:00:00 2001 From: EunbeenDev Date: Mon, 13 Jan 2025 23:51:15 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[REFACTOR]=20=EC=93=B0=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EC=95=8A=EB=8A=94=20=EC=98=88=EC=99=B8=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/exception/UserDuplicateException.java | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java diff --git a/src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java b/src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java deleted file mode 100644 index 4224b43..0000000 --- a/src/main/java/com/movelog/domain/user/exception/UserDuplicateException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.movelog.domain.user.exception; - - -import com.movelog.global.exception.DuplicateException; - -public class UserDuplicateException extends DuplicateException { - - public UserDuplicateException() { - super("U002", "이미 존재하는 사용자입니다."); - } -}