Skip to content

Commit

Permalink
Merge pull request #47 from Move-Log/develop
Browse files Browse the repository at this point in the history
Merge develop to main
  • Loading branch information
EunbeenDev authored Jan 13, 2025
2 parents 19ac66b + 6356304 commit cc46fdd
Showing 21 changed files with 329 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
@Service
@RequiredArgsConstructor
@Slf4j
public class HeadlineGeneratorService {
public class HeadLineGeneratorService {

private final GptService gptService;

59 changes: 50 additions & 9 deletions src/main/java/com/movelog/domain/news/application/NewsService.java
Original file line number Diff line number Diff line change
@@ -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<HeadLineRes> createHeadLine(UserPrincipal userPrincipal, NewsHeadLineReq newsHeadLineReq) {
public List<HeadLineRes> 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<User> userOptional = userService.findById(userPrincipal.getId());
DefaultAssert.isOptionalPresent(userOptional);
if (userOptional.isEmpty()) { throw new UserNotFoundException(); }
return userOptional.get();
}

// Keyword 정보 검증
private Keyword validateKeyword(Long keywordId) {
Optional<Keyword> keywordOptional = keywordRepository.findById(keywordId);
if (keywordOptional.isEmpty()) { throw new KeywordNotFoundException(); }
return keywordOptional.get();
}


}
38 changes: 38 additions & 0 deletions src/main/java/com/movelog/domain/news/domain/News.java
Original file line number Diff line number Diff line change
@@ -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;
}



}
Original file line number Diff line number Diff line change
@@ -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<News, Long> {
}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;


}
Original file line number Diff line number Diff line change
@@ -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<HeadLineRes> 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<HeadLineRes> 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()));
}






}
19 changes: 17 additions & 2 deletions src/main/java/com/movelog/domain/record/domain/Keyword.java
Original file line number Diff line number Diff line change
@@ -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,13 +22,26 @@ 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<Record> records = new ArrayList<>();

@OneToMany(mappedBy = "keyword")
private List<News> news = new ArrayList<>();

@Builder
public Keyword(String keyword) {
public Keyword(User user, String keyword, VerbType verbType) {
this.user = user;
this.keyword = keyword;
this.verbType = verbType;
}
}
11 changes: 1 addition & 10 deletions src/main/java/com/movelog/domain/record/domain/Record.java
Original file line number Diff line number Diff line change
@@ -20,28 +20,19 @@ 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;

@Enumerated(EnumType.STRING) // Enum을 문자열로 저장
private VerbType verbType;

@Column(name = "record_image")
private String recordImage;

@Column(name = "action_time")
private java.time.LocalDateTime actionTime;

@Builder
public Record(User user, Keyword keyword, VerbType verbType, String recordImage) {
this.user = user;
public Record(Keyword keyword, String recordImage) {
this.keyword = keyword;
this.verbType = verbType;
this.recordImage = recordImage;
this.actionTime = actionTime == null? LocalDateTime.now():actionTime;
}
5 changes: 5 additions & 0 deletions src/main/java/com/movelog/domain/record/domain/VerbType.java
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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", "키워드를 찾을 수 없습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ public ResponseEntity<ApiResponse> retrieveTodayRecord(

ApiResponse result = ApiResponse.builder()
.check(true)
.information(recordService.retrieveTodayRecord(userPrincipal.getId()))
.information(recordService.retrieveTodayRecord(5L))
.build();
return ResponseEntity.ok(result);
}
Original file line number Diff line number Diff line change
@@ -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<Keyword,Long> {
List<Keyword> findByUser(User user);
}
Original file line number Diff line number Diff line change
@@ -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<Record,Long> {
List<Record> findByUserAndActionTimeBetween(User user, LocalDateTime startOfDay, LocalDateTime endOfDay);
List<Record> findByKeywordInAndActionTimeBetween(List<Keyword> keywords, LocalDateTime startTime, LocalDateTime endTime);
}
Loading

0 comments on commit cc46fdd

Please sign in to comment.