diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml
index 9ea5484..e86d9ca 100644
--- a/.github/workflows/dev_deploy.yml
+++ b/.github/workflows/dev_deploy.yml
@@ -1,19 +1,17 @@
name: Breifing Dev CI/CD
on:
- push:
- branches:
- - develop
pull_request:
branches:
- develop
- types: [closed]
+ types:
+ - closed
workflow_dispatch: # (2).수동 실행도 가능하도록
jobs:
build:
runs-on: ubuntu-latest # (3).OS환경
- if: (github.event_name == 'push' && github.ref == 'refs/heads/develop') || (github.event.pull_request && github.event.pull_request.merged == true)
+ if: github.event.pull_request.merged == true
steps:
- name: Checkout
diff --git a/.github/workflows/release_deploy.yml b/.github/workflows/release_deploy.yml
index 25f52d1..6ce6b3f 100644
--- a/.github/workflows/release_deploy.yml
+++ b/.github/workflows/release_deploy.yml
@@ -1,20 +1,17 @@
name: Breifing Release CI/CD
on:
- push:
- branches:
- - release
pull_request:
branches:
- release
- types: [closed]
+ types:
+ - closed
workflow_dispatch: # (2).수동 실행도 가능하도록
jobs:
build:
runs-on: ubuntu-latest # (3).OS환경
- if: (github.event_name == 'push' && github.ref == 'refs/heads/release') || (github.event.pull_request && github.event.pull_request.merged == true)
-
+ if: github.event.pull_request.merged == true
steps:
- name: Checkout
diff --git a/README.md b/README.md
index 1c93a13..c99a517 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,110 @@
-# Briefing-Backend
\ No newline at end of file
+[![Briefing](https://github.com/Team-Shaka/Briefing-Backend/assets/53550707/d6a382f7-fee6-4a70-8ec7-5e9ce9803986)](https://linktr.ee/briefingnews)
+
+
+# Briefing
+
+> 당신의 AI 뉴스 리더, Briefing • 백엔드 리포지토리
+
+
+
+## 📰 서비스 소개
+
+
+
+
+![image](https://github.com/Team-Shaka/Briefing-Backend/assets/53550707/3fc54a19-3944-4168-a18f-701536ebe128)
+
+
+
+## 👨👩👧👦 팀 소개
+### Project Manager
+| **진윤겸** |
+| :------: |
+| [
@Younkyum](https://github.com/Younkyum) |
+
+### Web Developer
+| **서다원** | **조주희** |
+| :------: | :------: |
+| [
@Dawon00](https://github.com/Dawon00) | [
@juhui88](https://github.com/juhui88) |
+
+### Android Developer
+| **김경록** | **김민서** |
+| :------: | :------: |
+| [
@gomsang](https://github.com/gomsang) | [
@kimwest00](https://github.com/kimwest00) |
+
+### IOS Developer
+| **이보민** | **이전희** |
+| :------: | :------: |
+| [
@bome24](https://github.com/bome24) | [
@Jeonhui](https://github.com/Jeonhui) |
+
+### Server Developer
+| **권현재** | **정성훈** | **최용욱** |
+| :------: | :------: | :------: |
+| [
@hyeonjerry](https://github.com/hyeonjerry) | [
@swa07016](https://github.com/swa07016) | [
@CYY1007](https://github.com/CYY1007) |
+
+
+
+## 👨💻 기술 스택
+
+
+
+
+Spring Cloud
+- write
+
+QueryDSL
+- 컴파일 시점 문법 검사와 개발 편의성을 위해 QueryDSL을 사용했습니다.
+
+Redis
+- Refresh Token 관리를 위해 Redis를 사용했습니다.
+
+AWS
+- write
+
+
+
+## 📚 개발 과정
+
+
+
+## 📁 프로젝트 아키텍쳐
+
+
+ API 운영 Server |
+ API 개발 Server |
+
+
+ |
+ |
+
+
+ Crawler |
+ Cloud |
+
+
+ |
+ |
+
+
+ API Server CI/CD |
+ Cloud CI/CD |
+
+
+ |
+ |
+
+
+
diff --git a/build.gradle b/build.gradle
index 49d7e01..58cdea0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.1.2'
id 'io.spring.dependency-management' version '1.1.2'
+// id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
}
group = 'briefing.info'
@@ -23,6 +24,17 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+
+ // eureka client 설정
+ implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
+
+ // querydsl
+ // == 스프링 부트 3.0 이상 ==
+ implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
+ annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
+ annotationProcessor "jakarta.annotation:jakarta.annotation-api"
+ annotationProcessor "jakarta.persistence:jakarta.persistence-api"
+
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE'
@@ -46,6 +58,16 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
+def querydslSrcDir = 'src/main/generated'
+
+clean {
+ delete file(querydslSrcDir)
+}
+
+tasks.withType(JavaCompile) {
+ options.generatedSourceOutputDirectory = file(querydslSrcDir)
+}
+
tasks.named('test') {
useJUnitPlatform()
}
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/src/main/generated/briefing/base/QBaseDateTimeEntity.java b/src/main/generated/briefing/base/QBaseDateTimeEntity.java
new file mode 100644
index 0000000..55211cf
--- /dev/null
+++ b/src/main/generated/briefing/base/QBaseDateTimeEntity.java
@@ -0,0 +1,39 @@
+package briefing.base;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+
+
+/**
+ * QBaseDateTimeEntity is a Querydsl query type for BaseDateTimeEntity
+ */
+@Generated("com.querydsl.codegen.DefaultSupertypeSerializer")
+public class QBaseDateTimeEntity extends EntityPathBase {
+
+ private static final long serialVersionUID = 1261212710L;
+
+ public static final QBaseDateTimeEntity baseDateTimeEntity = new QBaseDateTimeEntity("baseDateTimeEntity");
+
+ public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class);
+
+ public final DateTimePath updatedAt = createDateTime("updatedAt", java.time.LocalDateTime.class);
+
+ public QBaseDateTimeEntity(String variable) {
+ super(BaseDateTimeEntity.class, forVariable(variable));
+ }
+
+ public QBaseDateTimeEntity(Path extends BaseDateTimeEntity> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QBaseDateTimeEntity(PathMetadata metadata) {
+ super(BaseDateTimeEntity.class, metadata);
+ }
+
+}
+
diff --git a/src/main/generated/briefing/briefing/domain/QArticle.java b/src/main/generated/briefing/briefing/domain/QArticle.java
new file mode 100644
index 0000000..db837a2
--- /dev/null
+++ b/src/main/generated/briefing/briefing/domain/QArticle.java
@@ -0,0 +1,51 @@
+package briefing.briefing.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+
+
+/**
+ * QArticle is a Querydsl query type for Article
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QArticle extends EntityPathBase {
+
+ private static final long serialVersionUID = -598074260L;
+
+ public static final QArticle article = new QArticle("article");
+
+ public final briefing.base.QBaseDateTimeEntity _super = new briefing.base.QBaseDateTimeEntity(this);
+
+ //inherited
+ public final DateTimePath createdAt = _super.createdAt;
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final StringPath press = createString("press");
+
+ public final StringPath title = createString("title");
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public final StringPath url = createString("url");
+
+ public QArticle(String variable) {
+ super(Article.class, forVariable(variable));
+ }
+
+ public QArticle(Path extends Article> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QArticle(PathMetadata metadata) {
+ super(Article.class, metadata);
+ }
+
+}
+
diff --git a/src/main/generated/briefing/briefing/domain/QBriefing.java b/src/main/generated/briefing/briefing/domain/QBriefing.java
new file mode 100644
index 0000000..3a93bce
--- /dev/null
+++ b/src/main/generated/briefing/briefing/domain/QBriefing.java
@@ -0,0 +1,62 @@
+package briefing.briefing.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QBriefing is a Querydsl query type for Briefing
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QBriefing extends EntityPathBase {
+
+ private static final long serialVersionUID = 63849586L;
+
+ public static final QBriefing briefing = new QBriefing("briefing");
+
+ public final briefing.base.QBaseDateTimeEntity _super = new briefing.base.QBaseDateTimeEntity(this);
+
+ public final ListPath briefingArticles = this.createList("briefingArticles", BriefingArticle.class, QBriefingArticle.class, PathInits.DIRECT2);
+
+ public final StringPath content = createString("content");
+
+ //inherited
+ public final DateTimePath createdAt = _super.createdAt;
+
+ public final EnumPath gptModel = createEnum("gptModel", briefing.chatting.domain.GptModel.class);
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final NumberPath ranks = createNumber("ranks", Integer.class);
+
+ public final StringPath subtitle = createString("subtitle");
+
+ public final EnumPath timeOfDay = createEnum("timeOfDay", TimeOfDay.class);
+
+ public final StringPath title = createString("title");
+
+ public final EnumPath type = createEnum("type", BriefingType.class);
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public QBriefing(String variable) {
+ super(Briefing.class, forVariable(variable));
+ }
+
+ public QBriefing(Path extends Briefing> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QBriefing(PathMetadata metadata) {
+ super(Briefing.class, metadata);
+ }
+
+}
+
diff --git a/src/main/generated/briefing/briefing/domain/QBriefingArticle.java b/src/main/generated/briefing/briefing/domain/QBriefingArticle.java
new file mode 100644
index 0000000..fb3152a
--- /dev/null
+++ b/src/main/generated/briefing/briefing/domain/QBriefingArticle.java
@@ -0,0 +1,62 @@
+package briefing.briefing.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QBriefingArticle is a Querydsl query type for BriefingArticle
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QBriefingArticle extends EntityPathBase {
+
+ private static final long serialVersionUID = 37002276L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QBriefingArticle briefingArticle = new QBriefingArticle("briefingArticle");
+
+ public final briefing.base.QBaseDateTimeEntity _super = new briefing.base.QBaseDateTimeEntity(this);
+
+ public final QArticle article;
+
+ public final QBriefing briefing;
+
+ //inherited
+ public final DateTimePath createdAt = _super.createdAt;
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public QBriefingArticle(String variable) {
+ this(BriefingArticle.class, forVariable(variable), INITS);
+ }
+
+ public QBriefingArticle(Path extends BriefingArticle> path) {
+ this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
+ }
+
+ public QBriefingArticle(PathMetadata metadata) {
+ this(metadata, PathInits.getFor(metadata, INITS));
+ }
+
+ public QBriefingArticle(PathMetadata metadata, PathInits inits) {
+ this(BriefingArticle.class, metadata, inits);
+ }
+
+ public QBriefingArticle(Class extends BriefingArticle> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.article = inits.isInitialized("article") ? new QArticle(forProperty("article")) : null;
+ this.briefing = inits.isInitialized("briefing") ? new QBriefing(forProperty("briefing")) : null;
+ }
+
+}
+
diff --git a/src/main/generated/briefing/chatting/domain/QChatting.java b/src/main/generated/briefing/chatting/domain/QChatting.java
new file mode 100644
index 0000000..0a9ccae
--- /dev/null
+++ b/src/main/generated/briefing/chatting/domain/QChatting.java
@@ -0,0 +1,50 @@
+package briefing.chatting.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QChatting is a Querydsl query type for Chatting
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QChatting extends EntityPathBase {
+
+ private static final long serialVersionUID = -1751649618L;
+
+ public static final QChatting chatting = new QChatting("chatting");
+
+ public final briefing.base.QBaseDateTimeEntity _super = new briefing.base.QBaseDateTimeEntity(this);
+
+ //inherited
+ public final DateTimePath createdAt = _super.createdAt;
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final ListPath messages = this.createList("messages", Message.class, QMessage.class, PathInits.DIRECT2);
+
+ public final StringPath title = createString("title");
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public QChatting(String variable) {
+ super(Chatting.class, forVariable(variable));
+ }
+
+ public QChatting(Path extends Chatting> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QChatting(PathMetadata metadata) {
+ super(Chatting.class, metadata);
+ }
+
+}
+
diff --git a/src/main/generated/briefing/chatting/domain/QMessage.java b/src/main/generated/briefing/chatting/domain/QMessage.java
new file mode 100644
index 0000000..d9196c9
--- /dev/null
+++ b/src/main/generated/briefing/chatting/domain/QMessage.java
@@ -0,0 +1,57 @@
+package briefing.chatting.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QMessage is a Querydsl query type for Message
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QMessage extends EntityPathBase {
+
+ private static final long serialVersionUID = -1226188129L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QMessage message = new QMessage("message");
+
+ public final QChatting chatting;
+
+ public final StringPath content = createString("content");
+
+ public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class);
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final EnumPath role = createEnum("role", MessageRole.class);
+
+ public QMessage(String variable) {
+ this(Message.class, forVariable(variable), INITS);
+ }
+
+ public QMessage(Path extends Message> path) {
+ this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
+ }
+
+ public QMessage(PathMetadata metadata) {
+ this(metadata, PathInits.getFor(metadata, INITS));
+ }
+
+ public QMessage(PathMetadata metadata, PathInits inits) {
+ this(Message.class, metadata, inits);
+ }
+
+ public QMessage(Class extends Message> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.chatting = inits.isInitialized("chatting") ? new QChatting(forProperty("chatting")) : null;
+ }
+
+}
+
diff --git a/src/main/generated/briefing/member/domain/QMember.java b/src/main/generated/briefing/member/domain/QMember.java
new file mode 100644
index 0000000..1f80880
--- /dev/null
+++ b/src/main/generated/briefing/member/domain/QMember.java
@@ -0,0 +1,60 @@
+package briefing.member.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QMember is a Querydsl query type for Member
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QMember extends EntityPathBase {
+
+ private static final long serialVersionUID = 2051544022L;
+
+ public static final QMember member = new QMember("member1");
+
+ public final briefing.base.QBaseDateTimeEntity _super = new briefing.base.QBaseDateTimeEntity(this);
+
+ //inherited
+ public final DateTimePath createdAt = _super.createdAt;
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final StringPath nickName = createString("nickName");
+
+ public final StringPath profileImgUrl = createString("profileImgUrl");
+
+ public final EnumPath role = createEnum("role", MemberRole.class);
+
+ public final ListPath scrapList = this.createList("scrapList", briefing.scrap.domain.Scrap.class, briefing.scrap.domain.QScrap.class, PathInits.DIRECT2);
+
+ public final StringPath socialId = createString("socialId");
+
+ public final EnumPath socialType = createEnum("socialType", SocialType.class);
+
+ public final EnumPath status = createEnum("status", MemberStatus.class);
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public QMember(String variable) {
+ super(Member.class, forVariable(variable));
+ }
+
+ public QMember(Path extends Member> path) {
+ super(path.getType(), path.getMetadata());
+ }
+
+ public QMember(PathMetadata metadata) {
+ super(Member.class, metadata);
+ }
+
+}
+
diff --git a/src/main/generated/briefing/scrap/domain/QScrap.java b/src/main/generated/briefing/scrap/domain/QScrap.java
new file mode 100644
index 0000000..13e31f8
--- /dev/null
+++ b/src/main/generated/briefing/scrap/domain/QScrap.java
@@ -0,0 +1,62 @@
+package briefing.scrap.domain;
+
+import static com.querydsl.core.types.PathMetadataFactory.*;
+
+import com.querydsl.core.types.dsl.*;
+
+import com.querydsl.core.types.PathMetadata;
+import javax.annotation.processing.Generated;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.PathInits;
+
+
+/**
+ * QScrap is a Querydsl query type for Scrap
+ */
+@Generated("com.querydsl.codegen.DefaultEntitySerializer")
+public class QScrap extends EntityPathBase {
+
+ private static final long serialVersionUID = -881090678L;
+
+ private static final PathInits INITS = PathInits.DIRECT2;
+
+ public static final QScrap scrap = new QScrap("scrap");
+
+ public final briefing.base.QBaseDateTimeEntity _super = new briefing.base.QBaseDateTimeEntity(this);
+
+ public final briefing.briefing.domain.QBriefing briefing;
+
+ //inherited
+ public final DateTimePath createdAt = _super.createdAt;
+
+ public final NumberPath id = createNumber("id", Long.class);
+
+ public final briefing.member.domain.QMember member;
+
+ //inherited
+ public final DateTimePath updatedAt = _super.updatedAt;
+
+ public QScrap(String variable) {
+ this(Scrap.class, forVariable(variable), INITS);
+ }
+
+ public QScrap(Path extends Scrap> path) {
+ this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS));
+ }
+
+ public QScrap(PathMetadata metadata) {
+ this(metadata, PathInits.getFor(metadata, INITS));
+ }
+
+ public QScrap(PathMetadata metadata, PathInits inits) {
+ this(Scrap.class, metadata, inits);
+ }
+
+ public QScrap(Class extends Scrap> type, PathMetadata metadata, PathInits inits) {
+ super(type, metadata, inits);
+ this.briefing = inits.isInitialized("briefing") ? new briefing.briefing.domain.QBriefing(forProperty("briefing")) : null;
+ this.member = inits.isInitialized("member") ? new briefing.member.domain.QMember(forProperty("member")) : null;
+ }
+
+}
+
diff --git a/src/main/java/briefing/briefing/api/BriefingApi.java b/src/main/java/briefing/briefing/api/BriefingApi.java
index 43c7c8e..5847219 100644
--- a/src/main/java/briefing/briefing/api/BriefingApi.java
+++ b/src/main/java/briefing/briefing/api/BriefingApi.java
@@ -3,31 +3,31 @@
import briefing.briefing.application.BriefingCommandService;
import briefing.briefing.application.BriefingQueryService;
import briefing.briefing.application.dto.*;
+import briefing.briefing.domain.Briefing;
import briefing.briefing.domain.BriefingType;
import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
+import briefing.common.enums.APIVersion;
import briefing.common.response.CommonResponse;
import briefing.member.domain.Member;
import briefing.scrap.application.ScrapQueryService;
import briefing.security.handler.annotation.AuthMember;
+
+import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
+
import lombok.RequiredArgsConstructor;
+import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-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.RequestParam;
-import org.springframework.web.bind.annotation.ResponseStatus;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
@Tag(name = "03-Briefing \uD83D\uDCF0",description = "브리핑 관련 API")
@RestController
-@RequestMapping("/briefings")
@RequiredArgsConstructor
public class BriefingApi {
@@ -35,34 +35,77 @@ public class BriefingApi {
private final BriefingCommandService briefingCommandService;
private final ScrapQueryService scrapQueryService;
- @GetMapping
+ @GetMapping("/v2/briefings")
+ @Operation(summary = "03-01Briefing \uD83D\uDCF0 브리핑 목록 조회 V2", description = "")
+ public CommonResponse findBriefingsV2(
+ @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params
+ ) {
+ List briefingList = briefingQueryService.findBriefings(params, APIVersion.V2);
+ return CommonResponse.onSuccess(BriefingConverter.toBriefingPreviewListDTOV2(params.getDate(), briefingList));
+ }
+
+ @GetMapping("/briefings")
+ @Parameter(name = "timeOfDay", hidden = true)
+ @Operation(summary = "03-01Briefing \uD83D\uDCF0 브리핑 목록 조회 V1", description = "")
public CommonResponse findBriefings(
- @RequestParam("type") final BriefingType type,
- @RequestParam("date") final LocalDate date
+ @ParameterObject @ModelAttribute BriefingRequestParam.BriefingPreviewListParam params
) {
- return CommonResponse.onSuccess(BriefingConverter.toBriefingPreviewListDTO(date, briefingQueryService.findBriefings(type, date)));
+
+ List briefingList = briefingQueryService.findBriefings(params, APIVersion.V1);
+ return CommonResponse.onSuccess(BriefingConverter.toBriefingPreviewListDTO(params.getDate(), briefingList));
}
- @GetMapping("/{id}")
+ @Deprecated
+ @Operation(summary = "키워드 전달 V2 임시 API", description = "키워드 전달 V2 임시 API 입니다. 응답은 무조건 동일합니다. type만 주신걸 담아서 드립니다.")
+ @ApiResponse(responseCode = "1000", description = "OK, 성공")
+ @GetMapping("/briefings/temp")
+ public CommonResponse findBriefingsV2Temp(
+ @RequestParam("type") final BriefingType type,
+ @RequestParam("date") final LocalDate date
+ ){
+ List idList = Arrays.asList(346L, 347L, 348L, 349L, 350L);
+ return CommonResponse.onSuccess(BriefingConverter.toBriefingPreviewV2TempListDTO(date,idList,type));
+ }
+
+ @GetMapping("/v2/briefings/{id}")
+ @Operation(summary = "03-02Briefing \uD83D\uDCF0 브리핑 단건 조회 V2", description = "")
@Parameter(name = "member", hidden = true)
- public CommonResponse findBriefing(@PathVariable final Long id, @AuthMember Member member) {
+ public CommonResponse findBriefingV2(
+ @PathVariable final Long id,
+ @AuthMember Member member
+ ) {
+
+ Boolean isScrap = Optional.ofNullable(member)
+ .map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id))
+ .orElseGet(() -> Boolean.FALSE);
+
+ Boolean isBriefingOpen = false;
+ Boolean isWarning = false;
+
+ return CommonResponse.onSuccess(BriefingConverter.toBriefingDetailDTOV2(briefingQueryService.findBriefing(id, APIVersion.V2), isScrap, isBriefingOpen, isWarning));
+ }
+
+ @GetMapping("/briefings/{id}")
+ @Parameter(name = "member", hidden = true)
+ @Operation(summary = "03-02Briefing \uD83D\uDCF0 브리핑 단건 조회 V1", description = "")
+ public CommonResponse findBriefing(
+ @PathVariable final Long id,
+ @AuthMember Member member
+ ) {
Boolean isScrap = Optional.ofNullable(member)
.map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id))
.orElseGet(() -> Boolean.FALSE);
- /*
- TODO
- 업데이트가 확정되면 로직을 거쳐 isBriefingOpen과 isWarning을 세팅해주어야합니다.
- */
Boolean isBriefingOpen = false;
Boolean isWarning = false;
- return CommonResponse.onSuccess(BriefingConverter.toBriefingDetailDTO(briefingQueryService.findBriefing(id), isScrap, isBriefingOpen, isWarning));
+ return CommonResponse.onSuccess(BriefingConverter.toBriefingDetailDTO(briefingQueryService.findBriefing(id, APIVersion.V1), isScrap, isBriefingOpen, isWarning));
}
- @PostMapping
+ @PostMapping("/briefings")
@ResponseStatus(HttpStatus.CREATED)
+ @Operation(summary = "03-03Briefing \uD83D\uDCF0 브리핑 등록", description = "")
public void createBriefing(@RequestBody final BriefingRequestDTO.BriefingCreate request) {
briefingCommandService.createBriefing(request);
}
diff --git a/src/main/java/briefing/briefing/api/BriefingConverter.java b/src/main/java/briefing/briefing/api/BriefingConverter.java
index 56160f3..f192e74 100644
--- a/src/main/java/briefing/briefing/api/BriefingConverter.java
+++ b/src/main/java/briefing/briefing/api/BriefingConverter.java
@@ -7,12 +7,23 @@
import briefing.briefing.domain.BriefingType;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
public class BriefingConverter {
+ public static BriefingResponseDTO.BriefingPreviewDTOV2 toBriefingPreviewDTOV2(Briefing briefing){
+ return BriefingResponseDTO.BriefingPreviewDTOV2.builder()
+ .id(briefing.getId())
+ .ranks(briefing.getRanks())
+ .title(briefing.getTitle())
+ .subtitle(briefing.getSubtitle())
+ .scrapCount(briefing.getScrapCount())
+ .build();
+ }
+
public static BriefingResponseDTO.BriefingPreviewDTO toBriefingPreviewDTO(Briefing briefing){
return BriefingResponseDTO.BriefingPreviewDTO.builder()
.id(briefing.getId())
@@ -22,12 +33,32 @@ public static BriefingResponseDTO.BriefingPreviewDTO toBriefingPreviewDTO(Briefi
.build();
}
+ private static LocalDateTime getPreviewListDTOCreatedAt(final LocalDate date, List briefingList) {
+ if(!briefingList.isEmpty()) {
+ return briefingList.get(0).getCreatedAt();
+ }
+ if(date != null) {
+ return date.atTime(3,0);
+ }
+ return LocalDateTime.now();
+ }
+
+ public static BriefingResponseDTO.BriefingPreviewListDTOV2 toBriefingPreviewListDTOV2(final LocalDate date, List briefingList){
+ final List briefingPreviewDTOList = briefingList.stream()
+ .map(BriefingConverter::toBriefingPreviewDTOV2).toList();
+
+ return BriefingResponseDTO.BriefingPreviewListDTOV2.builder()
+ .createdAt(getPreviewListDTOCreatedAt(date, briefingList))
+ .briefings(briefingPreviewDTOList)
+ .build();
+ }
+
public static BriefingResponseDTO.BriefingPreviewListDTO toBriefingPreviewListDTO(final LocalDate date, List briefingList){
final List briefingPreviewDTOList = briefingList.stream()
.map(BriefingConverter::toBriefingPreviewDTO).toList();
return BriefingResponseDTO.BriefingPreviewListDTO.builder()
- .createdAt(date.atTime(3,0))
+ .createdAt(getPreviewListDTOCreatedAt(date, briefingList))
.briefings(briefingPreviewDTOList)
.build();
}
@@ -65,13 +96,43 @@ public static BriefingResponseDTO.BriefingDetailDTO toBriefingDetailDTO(
.build();
}
+ public static BriefingResponseDTO.BriefingDetailDTOV2 toBriefingDetailDTOV2(
+ Briefing briefing,
+ Boolean isScrap,
+ Boolean isBriefingOpen,
+ Boolean isWarning
+ ){
+
+ List articleResponseDTOList = briefing.getBriefingArticles().stream()
+ .map(article -> toArticleResponseDTO(article.getArticle())).toList();
+
+ return BriefingResponseDTO.BriefingDetailDTOV2.builder()
+ .id(briefing.getId())
+ .ranks(briefing.getRanks())
+ .title(briefing.getTitle())
+ .subtitle(briefing.getSubtitle())
+ .content(briefing.getContent())
+ .date(briefing.getCreatedAt().toLocalDate())
+ .articles(articleResponseDTOList)
+ .isScrap(isScrap)
+ .isBriefingOpen(isBriefingOpen)
+ .isWarning(isWarning)
+ .scrapCount(briefing.getScrapCount())
+ .gptModel(briefing.getGptModel())
+ .timeOfDay(briefing.getTimeOfDay())
+ .type(briefing.getType())
+ .build();
+ }
+
public static Briefing toBriefing(BriefingRequestDTO.BriefingCreate request){
return Briefing.builder()
- .type(BriefingType.KOREA)
+ .type(request.getBriefingType())
.ranks(request.getRanks())
.title(request.getTitle())
.subtitle(request.getSubtitle())
.content(request.getContent())
+ .gptModel(request.getGptModel())
+ .timeOfDay(request.getTimeOfDay())
.build();
}
@@ -82,4 +143,61 @@ public static Article toArticle(BriefingRequestDTO.ArticleCreateDTO request){
.url(request.getUrl())
.build();
}
+
+
+ public static BriefingResponseDTO.BriefingPreviewV2TempDTO toBriefingPreviewV2TempDTO(Long id){
+ Integer rank = null;
+ String title = null;
+ String subTitle = null;
+ Integer scrapCount = null;
+
+ if (id.equals(346L)){
+ rank = 1;
+ title = "소셜 1";
+ subTitle = "브리핑 부제목 1";
+ scrapCount = 1234;
+ }
+ else if (id.equals(347L)){
+ rank = 2;
+ title = "소셜 2";
+ subTitle = "브리핑 부제목 2";
+ scrapCount = 123;
+ }else if(id.equals(348L)){
+ rank = 3;
+ title = "소셜 3";
+ subTitle = "브리핑 부제목 3";
+ scrapCount = 13;
+ }else if (id.equals(349L)){
+ rank = 4;
+ title = "소셜 4";
+ subTitle = "브리핑 부제목 4";
+ scrapCount = 12323;
+ }else if (id.equals(350L)){
+ rank = 5;
+ title = "소셜 5";
+ subTitle = "브리핑 부제목 5";
+ scrapCount = 123;
+ }
+
+ return BriefingResponseDTO.BriefingPreviewV2TempDTO.builder()
+ .id(id)
+ .ranks(rank)
+ .title(title)
+ .subtitle(subTitle)
+ .scrapCount(scrapCount)
+ .build();
+ }
+
+ public static BriefingResponseDTO.BriefingV2PreviewListDTO toBriefingPreviewV2TempListDTO(final LocalDate date, List temp, BriefingType briefingType){
+ List tempDTOList = temp.stream()
+ .map(
+ BriefingConverter::toBriefingPreviewV2TempDTO
+ ).toList();
+
+ return BriefingResponseDTO.BriefingV2PreviewListDTO.builder()
+ .createdAt(date.atTime(3,0))
+ .type(briefingType.getValue())
+ .briefings(tempDTOList)
+ .build();
+ }
}
diff --git a/src/main/java/briefing/briefing/application/BriefingQueryService.java b/src/main/java/briefing/briefing/application/BriefingQueryService.java
index 849a0ab..1679355 100644
--- a/src/main/java/briefing/briefing/application/BriefingQueryService.java
+++ b/src/main/java/briefing/briefing/application/BriefingQueryService.java
@@ -1,13 +1,12 @@
package briefing.briefing.application;
+import briefing.briefing.application.context.BriefingQueryContext;
+import briefing.briefing.application.context.BriefingQueryContextFactory;
+import briefing.briefing.application.dto.BriefingRequestParam;
import briefing.briefing.domain.Briefing;
-import briefing.briefing.domain.BriefingType;
-import briefing.briefing.domain.repository.BriefingRepository;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
import java.util.List;
+import briefing.common.enums.APIVersion;
import briefing.exception.ErrorCode;
import briefing.exception.handler.BriefingException;
import lombok.RequiredArgsConstructor;
@@ -19,22 +18,14 @@
@RequiredArgsConstructor
public class BriefingQueryService {
- private final BriefingRepository briefingRepository;
-
- public List findBriefings(final BriefingType type, final LocalDate date) {
- final LocalDateTime startDateTime = date.atStartOfDay();
- final LocalDateTime endDateTime = date.atTime(LocalTime.MAX);
-
- final List briefings = briefingRepository.findAllByTypeAndCreatedAtBetweenOrderByRanks(
- type, startDateTime, endDateTime);
-
- return briefings;
+ public List findBriefings(BriefingRequestParam.BriefingPreviewListParam params, APIVersion version) {
+ BriefingQueryContext briefingQueryContext = BriefingQueryContextFactory.getContextByVersion(version);
+ return briefingQueryContext.findBriefings(params);
}
- public Briefing findBriefing(final Long id) {
- final Briefing briefing = briefingRepository.findById(id)
+ public Briefing findBriefing(final Long id, final APIVersion version) {
+ BriefingQueryContext briefingQueryContext = BriefingQueryContextFactory.getContextByVersion(version);
+ return briefingQueryContext.findById(id)
.orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_BRIEFING));
-
- return briefing;
}
}
diff --git a/src/main/java/briefing/briefing/application/context/BriefingQueryContext.java b/src/main/java/briefing/briefing/application/context/BriefingQueryContext.java
new file mode 100644
index 0000000..896be83
--- /dev/null
+++ b/src/main/java/briefing/briefing/application/context/BriefingQueryContext.java
@@ -0,0 +1,22 @@
+package briefing.briefing.application.context;
+
+import briefing.briefing.application.dto.BriefingRequestParam;
+import briefing.briefing.application.strategy.BriefingQueryStrategy;
+import briefing.briefing.domain.Briefing;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+import java.util.Optional;
+
+@RequiredArgsConstructor
+public class BriefingQueryContext {
+ private final BriefingQueryStrategy briefingQueryStrategy;
+
+ public List findBriefings(BriefingRequestParam.BriefingPreviewListParam params) {
+ return this.briefingQueryStrategy.findBriefings(params);
+ }
+
+ public Optional findById(Long id) {
+ return this.briefingQueryStrategy.findById(id);
+ }
+}
diff --git a/src/main/java/briefing/briefing/application/context/BriefingQueryContextFactory.java b/src/main/java/briefing/briefing/application/context/BriefingQueryContextFactory.java
new file mode 100644
index 0000000..7e8fdaf
--- /dev/null
+++ b/src/main/java/briefing/briefing/application/context/BriefingQueryContextFactory.java
@@ -0,0 +1,37 @@
+package briefing.briefing.application.context;
+
+import briefing.briefing.application.strategy.BriefingQueryStrategy;
+import briefing.briefing.application.strategy.BriefingV1QueryStrategy;
+import briefing.briefing.application.strategy.BriefingV2QueryStrategy;
+import briefing.briefing.domain.repository.BriefingRepository;
+import briefing.common.enums.APIVersion;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class BriefingQueryContextFactory {
+
+ private final BriefingRepository briefingRepository;
+ private static BriefingQueryContext staticBriefingQueryContextV1;
+ private static BriefingQueryContext staticBriefingQueryContextV2;
+
+ @PostConstruct
+ private void init() {
+ staticBriefingQueryContextV1 = createContext(new BriefingV1QueryStrategy(briefingRepository));
+ staticBriefingQueryContextV2 = createContext(new BriefingV2QueryStrategy(briefingRepository));
+ }
+
+ private static BriefingQueryContext createContext(BriefingQueryStrategy strategy) {
+ return new BriefingQueryContext(strategy);
+ }
+
+ public static BriefingQueryContext getContextByVersion(APIVersion version) {
+ return switch (version) {
+ case V1 -> staticBriefingQueryContextV1;
+ case V2 -> staticBriefingQueryContextV2;
+ };
+ }
+}
+
diff --git a/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java b/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java
index b133194..a2389fa 100644
--- a/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java
+++ b/src/main/java/briefing/briefing/application/dto/BriefingRequestDTO.java
@@ -1,5 +1,8 @@
package briefing.briefing.application.dto;
+import briefing.briefing.domain.BriefingType;
+import briefing.briefing.domain.TimeOfDay;
+import briefing.chatting.domain.GptModel;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
@@ -21,5 +24,8 @@ public static class BriefingCreate{
String subtitle;
@JsonProperty("context") String content;
List articles;
+ GptModel gptModel = GptModel.GPT_4;
+ TimeOfDay timeOfDay = TimeOfDay.MORNING;
+ BriefingType briefingType = BriefingType.KOREA;
}
}
diff --git a/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java b/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java
new file mode 100644
index 0000000..196837f
--- /dev/null
+++ b/src/main/java/briefing/briefing/application/dto/BriefingRequestParam.java
@@ -0,0 +1,27 @@
+package briefing.briefing.application.dto;
+
+import briefing.briefing.domain.BriefingType;
+import briefing.briefing.domain.TimeOfDay;
+import jakarta.validation.constraints.NotNull;
+import lombok.*;
+
+import java.time.LocalDate;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class BriefingRequestParam {
+
+ @Builder
+ @Getter @Setter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BriefingPreviewListParam {
+ @NotNull
+ private BriefingType type;
+ private LocalDate date;
+ private TimeOfDay timeOfDay = TimeOfDay.MORNING;
+
+ public boolean isPresentDate() {
+ return date != null;
+ }
+ }
+}
diff --git a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java
index 6136bac..41876dc 100644
--- a/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java
+++ b/src/main/java/briefing/briefing/application/dto/BriefingResponseDTO.java
@@ -1,5 +1,8 @@
package briefing.briefing.application.dto;
+import briefing.briefing.domain.BriefingType;
+import briefing.briefing.domain.TimeOfDay;
+import briefing.chatting.domain.GptModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@@ -22,6 +25,19 @@ public static class ArticleResponseDTO{
String url;
}
+ @Builder
+ @Getter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BriefingPreviewDTOV2{
+ Long id;
+ Integer ranks;
+ String title;
+ String subtitle;
+ @Builder.Default
+ Integer scrapCount = 0;
+ }
+
@Builder
@Getter
@NoArgsConstructor
@@ -50,6 +66,40 @@ public static class BriefingDetailDTO{
Boolean isWarning;
}
+ @Builder
+ @Getter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BriefingDetailDTOV2{
+ Long id;
+ Integer ranks;
+ String title;
+ String subtitle;
+ String content;
+ LocalDate date;
+ List articles;
+ Boolean isScrap;
+ Boolean isBriefingOpen;
+ Boolean isWarning;
+ @Builder.Default
+ Integer scrapCount = 0;
+ @Builder.Default
+ GptModel gptModel = GptModel.GPT_3_5_TURBO;
+ @Builder.Default
+ TimeOfDay timeOfDay = TimeOfDay.MORNING;
+ @Builder.Default
+ BriefingType type = BriefingType.KOREA;
+ }
+
+ @Builder
+ @Getter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BriefingPreviewListDTOV2{
+ LocalDateTime createdAt;
+ List briefings;
+ }
+
@Builder
@Getter
@NoArgsConstructor
@@ -58,4 +108,26 @@ public static class BriefingPreviewListDTO{
LocalDateTime createdAt;
List briefings;
}
+
+ @Builder
+ @Getter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BriefingV2PreviewListDTO{
+ LocalDateTime createdAt;
+ String type;
+ List briefings;
+ }
+
+ @Builder
+ @Getter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class BriefingPreviewV2TempDTO{
+ Long id;
+ Integer ranks;
+ String title;
+ String subtitle;
+ Integer scrapCount;
+ }
}
diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingQueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingQueryStrategy.java
new file mode 100644
index 0000000..96c313a
--- /dev/null
+++ b/src/main/java/briefing/briefing/application/strategy/BriefingQueryStrategy.java
@@ -0,0 +1,13 @@
+package briefing.briefing.application.strategy;
+
+import briefing.briefing.application.dto.BriefingRequestParam;
+import briefing.briefing.domain.Briefing;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface BriefingQueryStrategy {
+ List findBriefings(BriefingRequestParam.BriefingPreviewListParam params);
+
+ Optional findById(Long id);
+}
diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java
new file mode 100644
index 0000000..283feac
--- /dev/null
+++ b/src/main/java/briefing/briefing/application/strategy/BriefingV1QueryStrategy.java
@@ -0,0 +1,33 @@
+package briefing.briefing.application.strategy;
+
+import briefing.briefing.application.dto.BriefingRequestParam;
+import briefing.briefing.domain.Briefing;
+import briefing.briefing.domain.BriefingType;
+import briefing.briefing.domain.repository.BriefingRepository;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
+import java.util.Optional;
+
+@RequiredArgsConstructor
+public class BriefingV1QueryStrategy implements BriefingQueryStrategy {
+
+ private final BriefingRepository briefingRepository;
+
+ @Override
+ public List findBriefings(BriefingRequestParam.BriefingPreviewListParam params) {
+ final LocalDateTime startDateTime = params.getDate().atStartOfDay();
+ final LocalDateTime endDateTime = params.getDate().atTime(LocalTime.MAX);
+
+ List briefingList = briefingRepository.findAllByTypeAndCreatedAtBetweenOrderByRanks(params.getType(), startDateTime, endDateTime);
+ if(briefingList.isEmpty()) return briefingRepository.findTop10ByTypeOrderByCreatedAtDesc(BriefingType.SOCIAL);
+ return briefingList;
+ }
+
+ @Override
+ public Optional findById(Long id) {
+ return briefingRepository.findById(id);
+ }
+}
diff --git a/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java b/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java
new file mode 100644
index 0000000..61bd421
--- /dev/null
+++ b/src/main/java/briefing/briefing/application/strategy/BriefingV2QueryStrategy.java
@@ -0,0 +1,38 @@
+package briefing.briefing.application.strategy;
+
+import briefing.briefing.application.dto.BriefingRequestParam;
+import briefing.briefing.domain.Briefing;
+import briefing.briefing.domain.repository.BriefingRepository;
+import lombok.RequiredArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+@RequiredArgsConstructor
+public class BriefingV2QueryStrategy implements BriefingQueryStrategy{
+
+ private final BriefingRepository briefingRepository;
+
+ @Override
+ public List findBriefings(BriefingRequestParam.BriefingPreviewListParam params) {
+ if(params.isPresentDate()) {
+ final LocalDateTime startDateTime = params.getDate().atStartOfDay();
+ final LocalDateTime endDateTime = params.getDate().atTime(LocalTime.MAX);
+
+ return briefingRepository.findBriefingsWithScrapCount(
+ params.getType(), startDateTime, endDateTime, params.getTimeOfDay());
+ }
+
+ List briefingList = briefingRepository.findTop10ByTypeOrderByCreatedAtDesc(params.getType());
+ Collections.reverse(briefingList);
+ return briefingList;
+ }
+
+ @Override
+ public Optional findById(Long id) {
+ return briefingRepository.findByIdWithScrapCount(id);
+ }
+}
diff --git a/src/main/java/briefing/briefing/domain/Briefing.java b/src/main/java/briefing/briefing/domain/Briefing.java
index 201eef2..e875d56 100644
--- a/src/main/java/briefing/briefing/domain/Briefing.java
+++ b/src/main/java/briefing/briefing/domain/Briefing.java
@@ -1,15 +1,9 @@
package briefing.briefing.domain;
import briefing.base.BaseDateTimeEntity;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.EnumType;
-import jakarta.persistence.Enumerated;
-import jakarta.persistence.FetchType;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.OneToMany;
+import briefing.chatting.domain.GptModel;
+import jakarta.persistence.*;
+
import java.util.ArrayList;
import java.util.List;
@@ -24,26 +18,40 @@ public class Briefing extends BaseDateTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
+
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private BriefingType type;
+
@Column(nullable = false)
private Integer ranks;
+
@Column(nullable = false)
private String title;
+
@Column(nullable = false)
private String subtitle;
+
@Column(nullable = false, length = 1000)
private String content;
+
+ @Builder.Default
@OneToMany(mappedBy = "briefing", fetch = FetchType.LAZY)
private List briefingArticles = new ArrayList<>();
-// public Briefing(final BriefingType type, final Integer ranks, final String title,
-// final String subtitle, final String content) {
-// this.type = type;
-// this.ranks = ranks;
-// this.title = title;
-// this.subtitle = subtitle;
-// this.content = content;
-// }
+ @Builder.Default
+ @Transient
+ private Integer scrapCount = 0;
+
+ @Builder.Default
+ @Enumerated(EnumType.STRING)
+ private TimeOfDay timeOfDay = TimeOfDay.MORNING;
+
+ @Builder.Default
+ @Enumerated(EnumType.STRING)
+ private GptModel gptModel = GptModel.GPT_3_5_TURBO;
+
+ public void setScrapCount(Integer scrapCount) {
+ this.scrapCount = scrapCount;
+ }
}
diff --git a/src/main/java/briefing/briefing/domain/BriefingType.java b/src/main/java/briefing/briefing/domain/BriefingType.java
index 4660d00..99e18de 100644
--- a/src/main/java/briefing/briefing/domain/BriefingType.java
+++ b/src/main/java/briefing/briefing/domain/BriefingType.java
@@ -11,7 +11,10 @@
@RequiredArgsConstructor
public enum BriefingType {
KOREA("Korea"),
- GLOBAL("Global");
+ GLOBAL("Global"),
+ SOCIAL("Social"),
+ SCIENCE("Science"),
+ ECONOMY("Economy");
private final String value;
diff --git a/src/main/java/briefing/briefing/domain/TimeOfDay.java b/src/main/java/briefing/briefing/domain/TimeOfDay.java
new file mode 100644
index 0000000..abd3fbb
--- /dev/null
+++ b/src/main/java/briefing/briefing/domain/TimeOfDay.java
@@ -0,0 +1,30 @@
+package briefing.briefing.domain;
+
+import briefing.exception.ErrorCode;
+import briefing.exception.handler.BriefingException;
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+@Getter
+@AllArgsConstructor
+public enum TimeOfDay {
+ MORNING("Morning"),
+ EVENING("Evening");
+
+ private final String description;
+
+ public static TimeOfDay findByValue(String type) {
+ return Arrays.stream(values())
+ .filter(value -> value.getDescription().equals(type))
+ .findAny()
+ .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_TYPE));
+ }
+
+ @JsonValue
+ String getTimeOfDay() {
+ return this.getDescription();
+ }
+}
diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepository.java b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepository.java
new file mode 100644
index 0000000..3c34718
--- /dev/null
+++ b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepository.java
@@ -0,0 +1,17 @@
+package briefing.briefing.domain.repository;
+
+import briefing.briefing.domain.Briefing;
+import briefing.briefing.domain.BriefingType;
+import briefing.briefing.domain.TimeOfDay;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+public interface BriefingCustomRepository {
+ List findBriefingsWithScrapCount(BriefingType type, LocalDateTime start, LocalDateTime end, TimeOfDay timeOfDay);
+
+ List findTop10ByTypeOrderByCreatedAtDesc(BriefingType type);
+
+ Optional findByIdWithScrapCount(Long id);
+}
diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java
new file mode 100644
index 0000000..d04bb07
--- /dev/null
+++ b/src/main/java/briefing/briefing/domain/repository/BriefingCustomRepositoryImpl.java
@@ -0,0 +1,101 @@
+package briefing.briefing.domain.repository;
+
+import briefing.briefing.domain.Briefing;
+import briefing.briefing.domain.BriefingType;
+import briefing.briefing.domain.QBriefing;
+import briefing.briefing.domain.TimeOfDay;
+import briefing.scrap.domain.QScrap;
+import com.querydsl.core.Tuple;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+
+@Repository
+@RequiredArgsConstructor
+public class BriefingCustomRepositoryImpl implements BriefingCustomRepository {
+ private final JPAQueryFactory queryFactory;
+
+ @Override
+ public List findBriefingsWithScrapCount(BriefingType type, LocalDateTime start, LocalDateTime end, TimeOfDay timeOfDay) {
+ QBriefing briefing = QBriefing.briefing;
+ QScrap scrap = QScrap.scrap;
+
+ // 쿼리 결과를 Tuple로 가져옵니다.
+ List results = queryFactory.select(briefing, scrap.count())
+ .from(briefing)
+ .leftJoin(scrap).on(scrap.briefing.eq(briefing))
+ .where(briefing.type.eq(type)
+ .and(briefing.createdAt.between(start, end))
+ .and(briefing.timeOfDay.eq(timeOfDay))
+ )
+ .groupBy(briefing)
+ .orderBy(briefing.ranks.asc())
+ .fetch();
+
+ // Tuple 결과를 Briefing과 scrapCount로 변환합니다.
+ return results.stream()
+ .map(tuple -> {
+ Briefing b = tuple.get(briefing);
+ b.setScrapCount(Math.toIntExact(tuple.get(scrap.count())));
+ return b;
+ })
+ .toList();
+ }
+
+ @Override
+ public List findTop10ByTypeOrderByCreatedAtDesc(BriefingType type) {
+ QBriefing briefing = QBriefing.briefing;
+ QScrap scrap = QScrap.scrap;
+
+ List results = queryFactory.select(briefing, scrap.count())
+ .from(briefing)
+ .leftJoin(scrap).on(scrap.briefing.eq(briefing))
+ .where(briefing.type.eq(type))
+ .groupBy(briefing)
+ .orderBy(briefing.createdAt.desc())
+ .limit(10)
+ .fetch();
+
+ return results.stream()
+ .map(tuple -> {
+ Briefing b = tuple.get(briefing);
+ b.setScrapCount(Math.toIntExact(tuple.get(scrap.count())));
+ return b;
+ })
+ .collect(Collectors.toCollection(ArrayList::new));
+ }
+
+
+ @Override
+ public Optional findByIdWithScrapCount(Long id) {
+ QBriefing briefing = QBriefing.briefing;
+ QScrap scrap = QScrap.scrap;
+
+ // 쿼리 결과를 Tuple로 가져옵니다.
+ Tuple result = queryFactory.select(briefing, scrap.count())
+ .from(briefing)
+ .leftJoin(scrap).on(scrap.briefing.eq(briefing))
+ .where(briefing.id.eq(id))
+ .groupBy(briefing)
+ .fetchOne(); // 단일 결과를 가져옵니다.
+
+ // 결과가 없으면 Optional.empty() 반환
+ if (result == null) {
+ return Optional.empty();
+ }
+
+ // Tuple 결과를 Briefing과 scrapCount로 변환합니다.
+ Briefing b = result.get(briefing);
+ b.setScrapCount(Math.toIntExact(result.get(scrap.count())));
+
+ return Optional.of(b);
+ }
+
+}
diff --git a/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java b/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java
index dc21463..1ccae49 100644
--- a/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java
+++ b/src/main/java/briefing/briefing/domain/repository/BriefingRepository.java
@@ -8,7 +8,7 @@
import org.springframework.stereotype.Repository;
@Repository
-public interface BriefingRepository extends JpaRepository {
+public interface BriefingRepository extends JpaRepository, BriefingCustomRepository {
List findAllByTypeAndCreatedAtBetweenOrderByRanks(BriefingType type, LocalDateTime start,
LocalDateTime end);
diff --git a/src/main/java/briefing/chatting/domain/GptModel.java b/src/main/java/briefing/chatting/domain/GptModel.java
index b8ab16f..1b34787 100644
--- a/src/main/java/briefing/chatting/domain/GptModel.java
+++ b/src/main/java/briefing/chatting/domain/GptModel.java
@@ -11,7 +11,8 @@
@Getter
@RequiredArgsConstructor
public enum GptModel {
- GPT_3_5_TURBO("gpt-3.5-turbo");
+ GPT_3_5_TURBO("gpt-3.5-turbo"),
+ GPT_4("gpt-4");
@JsonValue
private final String value;
diff --git a/src/main/java/briefing/common/enums/APIVersion.java b/src/main/java/briefing/common/enums/APIVersion.java
new file mode 100644
index 0000000..4908e64
--- /dev/null
+++ b/src/main/java/briefing/common/enums/APIVersion.java
@@ -0,0 +1,30 @@
+package briefing.common.enums;
+
+import briefing.exception.ErrorCode;
+import briefing.exception.handler.BriefingException;
+import com.fasterxml.jackson.annotation.JsonValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+@Getter
+@AllArgsConstructor
+public enum APIVersion {
+ V1("v1"),
+ V2("v2");
+
+ private final String version;
+
+ public static APIVersion findByValue(String type) {
+ return Arrays.stream(values())
+ .filter(value -> value.getVersion().equals(type))
+ .findAny()
+ .orElseThrow(() -> new BriefingException(ErrorCode.NOT_FOUND_TYPE));
+ }
+
+ @JsonValue
+ String getAPIVersion() {
+ return this.getVersion();
+ }
+}
diff --git a/src/main/java/briefing/config/GlobalWebConfig.java b/src/main/java/briefing/config/GlobalWebConfig.java
index bfc619a..1623242 100644
--- a/src/main/java/briefing/config/GlobalWebConfig.java
+++ b/src/main/java/briefing/config/GlobalWebConfig.java
@@ -1,9 +1,6 @@
package briefing.config;
-import briefing.converter.BriefingTypeRequestConverter;
-import briefing.converter.GptModelRequestConverter;
-import briefing.converter.MessageRoleRequestConverter;
-import briefing.converter.SocialTypeRequestConverter;
+import briefing.converter.*;
import briefing.security.handler.annotation.AuthUserArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
@@ -29,11 +26,18 @@ public void addFormatters(final FormatterRegistry registry) {
registry.addConverter(new GptModelRequestConverter());
registry.addConverter(new MessageRoleRequestConverter());
registry.addConverter(new SocialTypeRequestConverter());
+ registry.addConverter(new TimeOfDayConverter());
+ registry.addConverter(new APIVersionRequestConverter());
}
@Override
public void addCorsMappings(final CorsRegistry registry) {
registry.addMapping("/**")
- .allowedOrigins("http://localhost:3000", "https://briefing-web.vercel.app","https://dev.newsbreifing.store");
+ .allowedOrigins("*")
+ .allowedMethods("*")
+ .allowedHeaders("*")
+ .allowCredentials(false)
+ .maxAge(6000);
+
}
}
diff --git a/src/main/java/briefing/config/QueryDslConfig.java b/src/main/java/briefing/config/QueryDslConfig.java
new file mode 100644
index 0000000..fded353
--- /dev/null
+++ b/src/main/java/briefing/config/QueryDslConfig.java
@@ -0,0 +1,20 @@
+package briefing.config;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import jakarta.persistence.EntityManager;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@RequiredArgsConstructor
+public class QueryDslConfig {
+
+
+ private final EntityManager em;
+
+ @Bean
+ public JPAQueryFactory jpaQueryFactory(){
+ return new JPAQueryFactory(em);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/briefing/converter/APIVersionRequestConverter.java b/src/main/java/briefing/converter/APIVersionRequestConverter.java
new file mode 100644
index 0000000..fab93fc
--- /dev/null
+++ b/src/main/java/briefing/converter/APIVersionRequestConverter.java
@@ -0,0 +1,12 @@
+package briefing.converter;
+
+import briefing.common.enums.APIVersion;
+import lombok.NonNull;
+import org.springframework.core.convert.converter.Converter;
+public class APIVersionRequestConverter implements Converter{
+
+ @Override
+ public APIVersion convert(@NonNull final String source) {
+ return APIVersion.findByValue(source);
+ }
+}
diff --git a/src/main/java/briefing/converter/TimeOfDayConverter.java b/src/main/java/briefing/converter/TimeOfDayConverter.java
new file mode 100644
index 0000000..fa0116e
--- /dev/null
+++ b/src/main/java/briefing/converter/TimeOfDayConverter.java
@@ -0,0 +1,12 @@
+package briefing.converter;
+
+import briefing.briefing.domain.TimeOfDay;
+import lombok.NonNull;
+import org.springframework.core.convert.converter.Converter;
+
+public class TimeOfDayConverter implements Converter {
+ @Override
+ public TimeOfDay convert(@NonNull final String source) {
+ return TimeOfDay.findByValue(source);
+ }
+}
diff --git a/src/main/java/briefing/exception/ErrorCode.java b/src/main/java/briefing/exception/ErrorCode.java
index 8c2cd05..81f97ef 100644
--- a/src/main/java/briefing/exception/ErrorCode.java
+++ b/src/main/java/briefing/exception/ErrorCode.java
@@ -56,6 +56,7 @@ public enum ErrorCode {
// scrap 에러
SCRAP_ALREADY_EXISTS(CONFLICT, "SCRAP_001", "이미 스크랩했습니다."),
SCRAP_NOT_FOUND(NOT_FOUND, "SCRAP_002", "존재하지 않는 스크랩입니다."),
+ DUPLICATE_SCRAP(CONFLICT, "SCRAP_003", "중복된 스크랩 요청입니다."),
// briefing 에러
NOT_FOUND_BRIEFING(NOT_FOUND,"BRIEFING_001", "브리핑이 존재하지 않습니다."),
diff --git a/src/main/java/briefing/member/api/MemberApi.java b/src/main/java/briefing/member/api/MemberApi.java
index 3809280..e19d8d7 100644
--- a/src/main/java/briefing/member/api/MemberApi.java
+++ b/src/main/java/briefing/member/api/MemberApi.java
@@ -33,7 +33,6 @@
@Tag(name = "02-Member \uD83D\uDC64",description = "사용자 관련 API")
@RestController
@Validated
-@RequestMapping("/members")
@RequiredArgsConstructor
@ApiResponses({
@ApiResponse(responseCode = "COMMON000", description = "SERVER ERROR, 백앤드 개발자에게 알려주세요", content = @Content(schema = @Schema(implementation = CommonResponse.class))),
@@ -47,7 +46,7 @@ public class MemberApi {
private final RedisService redisService;
@Operation(summary = "Member\uD83D\uDC64 토큰 잘 발급되나 테스트용API", description = "테스트 용")
- @GetMapping("/auth/test")
+ @GetMapping("/members/auth/test")
public CommonResponse testGenerateToken(){
Member member = memberQueryService.testForTokenApi();
String accessToken = tokenProvider.createAccessToken(member.getId(), member.getSocialType().toString() ,member.getSocialId(), Arrays.asList(new SimpleGrantedAuthority(member.getRole().name())));
@@ -55,27 +54,38 @@ public CommonResponse testGenerateToken(){
return CommonResponse.onSuccess(MemberConverter.toLoginDTO(member,accessToken, refreshToken.getToken()));
}
- @Operation(summary = "02-01 Member\uD83D\uDC64 소셜 로그인 #FRAME", description = "구글, 애플 소셜로그인 API입니다.")
- @PostMapping("/auth/{socialType}")
+ @Operation(summary = "02-01 Member\uD83D\uDC64 소셜 로그인 V1", description = "구글, 애플 소셜로그인 API입니다.")
+ @PostMapping("/members/auth/{socialType}")
public CommonResponse login(
@Parameter(description = "소셜로그인 종류", example = "google") @PathVariable final SocialType socialType,
@RequestBody final MemberRequest.LoginDTO request
) {
Member member = memberCommandService.login(socialType, request);
- // TODO - TokenProvider에서 발급해주도록 변경
String accessToken = tokenProvider.createAccessToken(member.getId(),member.getSocialType().toString() ,member.getSocialId(), List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name())));
String refreshToken = redisService.generateRefreshToken(member.getSocialId(),member.getSocialType()).getToken();
return CommonResponse.onSuccess(MemberConverter.toLoginDTO(member, accessToken, refreshToken));
}
- @Operation(summary = "02-01 Member\uD83D\uDC64 accessToken 재발급 받기", description = "accessToken 만료 시 refreshToken으로 재발급을 받는 API 입니다.")
+ @Operation(summary = "02-01 Member\uD83D\uDC64 소셜 로그인 V2", description = "구글, 애플 소셜로그인 API입니다.")
+ @PostMapping("/v2/members/auth/{socialType}")
+ public CommonResponse loginV2(
+ @Parameter(description = "소셜로그인 종류", example = "google") @PathVariable final SocialType socialType,
+ @RequestBody final MemberRequest.LoginDTO request
+ ) {
+ Member member = memberCommandService.login(socialType, request);
+ String accessToken = tokenProvider.createAccessToken(member.getId(),member.getSocialType().toString() ,member.getSocialId(), List.of(new SimpleGrantedAuthority(MemberRole.ROLE_USER.name())));
+ String refreshToken = redisService.generateRefreshToken(member.getSocialId(),member.getSocialType()).getToken();
+ return CommonResponse.onSuccess(MemberConverter.toLoginDTO(member, accessToken, refreshToken));
+ }
+
+ @Operation(summary = "02-01 Member\uD83D\uDC64 accessToken 재발급 받기 V1", description = "accessToken 만료 시 refreshToken으로 재발급을 받는 API 입니다.")
@ApiResponses({
@ApiResponse(responseCode = "1000",description = "OK, 성공"),
@ApiResponse(responseCode = "COMMON001", description = "request body에 담길 값이 이상함, result를 확인해주세요!",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
@ApiResponse(responseCode = "AUTH005", description = "리프레시 토큰도 만료, 다시 로그인",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
@ApiResponse(responseCode = "AUTH009", description = "리프레시 토큰 모양이 잘못 됨",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
})
- @PostMapping("/auth/token")
+ @PostMapping("/members/auth/token")
public CommonResponse reissueToken(@Valid @RequestBody MemberRequest.ReissueDTO request){
RefreshToken refreshToken = redisService.reGenerateRefreshToken(request);
Member parsedMember = memberCommandService.parseRefreshToken(refreshToken);
@@ -83,8 +93,23 @@ public CommonResponse reissueToken(@Valid @Reque
return CommonResponse.onSuccess(MemberConverter.toReIssueTokenDTO(parsedMember.getId(), accessToken,refreshToken.getToken()));
}
- @Operation(summary = "02-01 Member\uD83D\uDC64 회원 탈퇴", description = "회원 탈퇴 API 입니다.")
- @DeleteMapping("/{memberId}")
+ @Operation(summary = "02-01 Member\uD83D\uDC64 accessToken 재발급 받기 V2", description = "accessToken 만료 시 refreshToken으로 재발급을 받는 API 입니다.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "1000",description = "OK, 성공"),
+ @ApiResponse(responseCode = "COMMON001", description = "request body에 담길 값이 이상함, result를 확인해주세요!",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ @ApiResponse(responseCode = "AUTH005", description = "리프레시 토큰도 만료, 다시 로그인",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ @ApiResponse(responseCode = "AUTH009", description = "리프레시 토큰 모양이 잘못 됨",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ })
+ @PostMapping("/v2/members/auth/token")
+ public CommonResponse reissueTokenV2(@Valid @RequestBody MemberRequest.ReissueDTO request){
+ RefreshToken refreshToken = redisService.reGenerateRefreshToken(request);
+ Member parsedMember = memberCommandService.parseRefreshToken(refreshToken);
+ String accessToken = tokenProvider.createAccessToken(parsedMember.getId(),parsedMember.getSocialType().toString(), parsedMember.getSocialId(), List.of(new SimpleGrantedAuthority(parsedMember.getRole().toString())));
+ return CommonResponse.onSuccess(MemberConverter.toReIssueTokenDTO(parsedMember.getId(), accessToken,refreshToken.getToken()));
+ }
+
+ @Operation(summary = "02-01 Member\uD83D\uDC64 회원 탈퇴 V1", description = "회원 탈퇴 API 입니다.")
+ @DeleteMapping("/members/{memberId}")
@Parameters({
@Parameter(name = "member", hidden = true),
@Parameter(name = "memberId", description = "삭제 대상 멤버아이디")
@@ -101,4 +126,23 @@ public CommonResponse quitMember(@AuthMember Member memb
memberCommandService.deleteMember(memberId);
return CommonResponse.onSuccess(MemberConverter.toQuitDTO());
}
+
+ @Operation(summary = "02-01 Member\uD83D\uDC64 회원 탈퇴 V2", description = "회원 탈퇴 API 입니다.")
+ @DeleteMapping("/v2/members/{memberId}")
+ @Parameters({
+ @Parameter(name = "member", hidden = true),
+ @Parameter(name = "memberId", description = "삭제 대상 멤버아이디")
+ })
+ @ApiResponses({
+ @ApiResponse(responseCode = "1000",description = "OK, 성공"),
+ @ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요!",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ @ApiResponse(responseCode = "AUTH004", description = "acess 토큰 만료",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ @ApiResponse(responseCode = "AUTH006", description = "acess 토큰 모양이 이상함",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ @ApiResponse(responseCode = "MEMBER_001", description = "사용자가 존재하지 않습니다.",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ @ApiResponse(responseCode = "MEMBER_002", description = "로그인 한 사용자와 탈퇴 대상이 동일하지 않습니다.",content = @Content(schema = @Schema(implementation = CommonResponse.class))),
+ })
+ public CommonResponse quitMemberV2(@AuthMember Member member, @CheckSameMember @PathVariable Long memberId){
+ memberCommandService.deleteMember(memberId);
+ return CommonResponse.onSuccess(MemberConverter.toQuitDTO());
+ }
}
diff --git a/src/main/java/briefing/member/domain/Member.java b/src/main/java/briefing/member/domain/Member.java
index 071d8e2..93e6722 100644
--- a/src/main/java/briefing/member/domain/Member.java
+++ b/src/main/java/briefing/member/domain/Member.java
@@ -27,13 +27,16 @@ public class Member extends BaseDateTimeEntity {
@Enumerated(EnumType.STRING)
private SocialType socialType;
+ @Builder.Default
@Enumerated(EnumType.STRING)
private MemberStatus status = MemberStatus.INACTIVE;
+ @Builder.Default
@Enumerated(EnumType.STRING)
private MemberRole role = MemberRole.ROLE_USER;
// cascade 설정
+ @Builder.Default
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List scrapList = new ArrayList<>();
}
diff --git a/src/main/java/briefing/scrap/api/ScrapApi.java b/src/main/java/briefing/scrap/api/ScrapApi.java
index 3761c4a..3f91143 100644
--- a/src/main/java/briefing/scrap/api/ScrapApi.java
+++ b/src/main/java/briefing/scrap/api/ScrapApi.java
@@ -15,30 +15,51 @@
@Tag(name = "05-Scrap 📁", description = "스크랩 관련 API")
@RestController
-@RequestMapping("/scraps")
@RequiredArgsConstructor
public class ScrapApi {
private final ScrapQueryService scrapQueryService;
private final ScrapCommandService scrapCommandService;
- @Operation(summary = "05-01 Scrap📁 스크랩하기 #FRAME", description = "브리핑을 스크랩하는 API입니다.")
- @PostMapping("/briefings")
+ @Operation(summary = "05-01 Scrap📁 스크랩하기 V1", description = "브리핑을 스크랩하는 API입니다.")
+ @PostMapping("/scraps/briefings")
public CommonResponse create(@RequestBody ScrapRequest.CreateDTO request) {
Scrap createdScrap = scrapCommandService.create(request);
return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap));
}
- @Operation(summary = "05-02 Scrap📁 스크랩 취소 #FRAME", description = "스크랩을 취소하는 API입니다.")
- @DeleteMapping("/briefings/{briefingId}/members/{memberId}")
+ @Operation(summary = "05-01 Scrap📁 스크랩하기 V2", description = "브리핑을 스크랩하는 API입니다.")
+ @PostMapping("/v2/scraps/briefings")
+ public CommonResponse createV2(@RequestBody ScrapRequest.CreateDTO request) {
+ Scrap createdScrap = scrapCommandService.create(request);
+ return CommonResponse.onSuccess(ScrapConverter.toCreateDTO(createdScrap));
+ }
+
+ @Operation(summary = "05-02 Scrap📁 스크랩 취소 V1", description = "스크랩을 취소하는 API입니다.")
+ @DeleteMapping("/scraps/briefings/{briefingId}/members/{memberId}")
public CommonResponse delete(@PathVariable Long briefingId, @PathVariable Long memberId) {
Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId);
return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap));
}
- @Operation(summary = "05-03 Scrap📁 내 스크랩 조회 #FRAME", description = "내 스크랩을 조회하는 API입니다.")
- @GetMapping("/briefings/members/{memberId}")
+ @Operation(summary = "05-02 Scrap📁 스크랩 취소 V2", description = "스크랩을 취소하는 API입니다.")
+ @DeleteMapping("/v2/scraps/briefings/{briefingId}/members/{memberId}")
+ public CommonResponse deleteV2(@PathVariable Long briefingId, @PathVariable Long memberId) {
+ Scrap deletedScrap = scrapCommandService.delete(briefingId, memberId);
+ return CommonResponse.onSuccess(ScrapConverter.toDeleteDTO(deletedScrap));
+ }
+
+
+ @Operation(summary = "05-03 Scrap📁 내 스크랩 조회 V1", description = "내 스크랩을 조회하는 API입니다.")
+ @GetMapping("/scraps/briefings/members/{memberId}")
public CommonResponse> getScrapsByMember(@PathVariable Long memberId) {
List scraps = scrapQueryService.getScrapsByMemberId(memberId);
return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTO).toList());
}
+
+ @Operation(summary = "05-03 Scrap📁 내 스크랩 조회 V2", description = "내 스크랩을 조회하는 API입니다.")
+ @GetMapping("/v2/scraps/briefings/members/{memberId}")
+ public CommonResponse> getScrapsByMemberV2(@PathVariable Long memberId) {
+ List scraps = scrapQueryService.getScrapsByMemberId(memberId);
+ return CommonResponse.onSuccess(scraps.stream().map(ScrapConverter::toReadDTOV2).toList());
+ }
}
diff --git a/src/main/java/briefing/scrap/api/ScrapConverter.java b/src/main/java/briefing/scrap/api/ScrapConverter.java
index 8cbab78..d55d031 100644
--- a/src/main/java/briefing/scrap/api/ScrapConverter.java
+++ b/src/main/java/briefing/scrap/api/ScrapConverter.java
@@ -40,4 +40,16 @@ public static ScrapResponse.ReadDTO toReadDTO(Scrap scrap) {
.date(scrap.getBriefing().getCreatedAt().toLocalDate())
.build();
}
+
+ public static ScrapResponse.ReadDTOV2 toReadDTOV2(Scrap scrap) {
+ return ScrapResponse.ReadDTOV2.builder()
+ .briefingId(scrap.getBriefing().getId())
+ .ranks(scrap.getBriefing().getRanks())
+ .title(scrap.getBriefing().getTitle())
+ .subtitle(scrap.getBriefing().getSubtitle())
+ .date(scrap.getBriefing().getCreatedAt().toLocalDate())
+ .gptModel(scrap.getBriefing().getGptModel())
+ .timeOfDay(scrap.getBriefing().getTimeOfDay())
+ .build();
+ }
}
diff --git a/src/main/java/briefing/scrap/application/ScrapCommandService.java b/src/main/java/briefing/scrap/application/ScrapCommandService.java
index bcbea3c..49e0849 100644
--- a/src/main/java/briefing/scrap/application/ScrapCommandService.java
+++ b/src/main/java/briefing/scrap/application/ScrapCommandService.java
@@ -13,6 +13,7 @@
import briefing.scrap.domain.repository.ScrapRepository;
import briefing.scrap.exception.ScrapException;
import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -40,7 +41,13 @@ public Scrap create(ScrapRequest.CreateDTO request) {
Scrap scrap = ScrapConverter.toScrap(member, briefing);
// Scrap 엔티티 저장 및 반환
- return scrapRepository.save(scrap);
+ try {
+ // Scrap 엔티티 저장 및 반환
+ return scrapRepository.save(scrap);
+ } catch (DataIntegrityViolationException e) {
+ // 중복 스크랩 예외 처리
+ throw new ScrapException(ErrorCode.DUPLICATE_SCRAP);
+ }
}
public Scrap delete(Long briefingId, Long memberId) {
diff --git a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java
index 27fcf50..2b4a0ec 100644
--- a/src/main/java/briefing/scrap/application/dto/ScrapResponse.java
+++ b/src/main/java/briefing/scrap/application/dto/ScrapResponse.java
@@ -1,5 +1,7 @@
package briefing.scrap.application.dto;
+import briefing.briefing.domain.TimeOfDay;
+import briefing.chatting.domain.GptModel;
import jakarta.persistence.Column;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -31,7 +33,6 @@ public static class DeleteDTO {
private LocalDateTime deletedAt;
}
-
@Builder
@Getter
@NoArgsConstructor
@@ -43,4 +44,20 @@ public static class ReadDTO {
private String subtitle;
private LocalDate date;
}
+
+ @Builder
+ @Getter
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class ReadDTOV2 {
+ private Long briefingId;
+ private Integer ranks;
+ private String title;
+ private String subtitle;
+ private LocalDate date;
+ @Builder.Default
+ private GptModel gptModel = GptModel.GPT_3_5_TURBO;
+ @Builder.Default
+ private TimeOfDay timeOfDay = TimeOfDay.MORNING;
+ }
}
diff --git a/src/main/java/briefing/scrap/domain/Scrap.java b/src/main/java/briefing/scrap/domain/Scrap.java
index 5063019..08d3a9d 100644
--- a/src/main/java/briefing/scrap/domain/Scrap.java
+++ b/src/main/java/briefing/scrap/domain/Scrap.java
@@ -10,6 +10,9 @@
@Getter @Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
+@Table(uniqueConstraints = {
+ @UniqueConstraint(columnNames = {"member_id", "briefing_id"})
+})
public class Scrap extends BaseDateTimeEntity {
@Id
diff --git a/src/main/java/briefing/security/config/SecurityConfig.java b/src/main/java/briefing/security/config/SecurityConfig.java
index 8f3bb4e..d16a5b3 100644
--- a/src/main/java/briefing/security/config/SecurityConfig.java
+++ b/src/main/java/briefing/security/config/SecurityConfig.java
@@ -70,7 +70,7 @@ public WebSecurityCustomizer webSecurityCustomizer() {
"/v3/api-docs/**",
"/swagger-ui/index.html",
"/swagger-ui/**",
- "/docs/**");
+ "/docs/**","/briefings/temp");
}
@Bean
@@ -82,10 +82,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.sessionManagement(manage -> manage.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Session 사용 안함
.formLogin(AbstractHttpConfigurer::disable) // form login 사용 안함
.authorizeHttpRequests(authorize -> {
+ authorize.requestMatchers("/v2/briefings/**").permitAll(); // 모두 접근 가능합니다.
authorize.requestMatchers("/briefings/**").permitAll(); // 모두 접근 가능합니다.
+ authorize.requestMatchers("/v2/members/auth/**").permitAll();
authorize.requestMatchers("/members/auth/**").permitAll();
authorize.requestMatchers("/chattings/**").permitAll();
+ authorize.requestMatchers(HttpMethod.DELETE, "/v2/members/{memberId}").authenticated();
authorize.requestMatchers(HttpMethod.DELETE, "/members/{memberId}").authenticated();
+ authorize.requestMatchers("/v2/scraps/**").authenticated();
authorize.requestMatchers("/scraps/**").authenticated();
authorize.anyRequest().authenticated();
})
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 9659f48..16dff82 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,5 +1,7 @@
# default profile
spring:
+ application:
+ name: briefing-dev
profiles:
active: dev
springdoc:
@@ -9,7 +11,6 @@ springdoc:
cache:
disabled: true
use-fqn: true
-
---
spring:
config:
@@ -27,8 +28,8 @@ spring:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
- # show_sql: true
- # format_sql: true
+ show_sql: true
+ format_sql: true
use_sql_comments: true
hbm2ddl:
auto: update
@@ -38,6 +39,16 @@ spring:
redis:
host: localhost
port: 6379
+eureka:
+ client:
+ service-url:
+ defaultZone: http://gateway.newsbreifing.store:8761/eureka
+ instance:
+ # 표기되는 규칙 변경
+ instance-id: dev_briefing_server
+ hostname: dev.newsbreifing.store
+ ip-address: dev.newsbreifing.store
+
jwt:
header: Authorization
# dev server
@@ -53,6 +64,8 @@ openai:
---
spring:
+ application:
+ name: dev
config:
activate:
on-profile: dev
@@ -76,8 +89,18 @@ spring:
default_batch_fetch_size: 1000
data:
redis:
- host: breifing-redis-cluster.bjyb5r.ng.0001.apne1.cache.amazonaws.com
+ host: ${REDIS_URL}
port: 6379
+eureka:
+ instance:
+ # 표기되는 규칙 변경
+ instance-id: dev_briefing_server
+ hostname: dev.newsbreifing.store
+ ip-address: dev.newsbreifing.store
+ client:
+ service-url:
+ defaultZone: http://gateway.newsbreifing.store:8761/eureka
+
jwt:
header: Authorization
# dev server
@@ -91,7 +114,6 @@ openai:
token: ${OPEN_API_TOKEN}
url:
chat: https://api.openai.com/v1/chat/completions
-
---
spring:
config:
@@ -117,7 +139,7 @@ spring:
default_batch_fetch_size: 1000
data:
redis:
- host: briefing-release-redis.0ot2cs.ng.0001.apn2.cache.amazonaws.com
+ host: ${release.redis.host}
port: 6379
jwt:
header: Authorization