From 46e15095a72be0a8e06c1560b84bee55feae6ce6 Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 00:40:26 +0900 Subject: [PATCH 01/60] =?UTF-8?q?feat:=20=EB=B6=84=EC=95=BC=EB=B3=84=20?= =?UTF-8?q?=EB=89=B4=EC=8A=A4=20=ED=86=B5=EA=B3=84=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice.xcodeproj/project.pbxproj | 28 ++++++ .../View/Report/Type/TypeReportModel.swift | 21 ++++ .../View/Report/Type/TypeReportView.swift | 96 +++++++++++++++++++ .../Report/Type/TypeReportViewModel.swift | 27 ++++++ 4 files changed, 172 insertions(+) create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index d2c4dcb0..0f78e940 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -58,6 +58,9 @@ 6CDB29FB2BAA07B10081037B /* GPTChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDB29FA2BAA07B10081037B /* GPTChatViewModel.swift */; }; 6CDB29FD2BAA07FD0081037B /* GPTChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDB29FC2BAA07FD0081037B /* GPTChatView.swift */; }; 6CDB29FF2BAA08280081037B /* GPTChatListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDB29FE2BAA08280081037B /* GPTChatListViewModel.swift */; }; + 6CE1030C2BD56A4000498AA4 /* TypeReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030B2BD56A4000498AA4 /* TypeReportView.swift */; }; + 6CE1030E2BD56A5200498AA4 /* TypeReportModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */; }; + 6CE103102BD56A5B00498AA4 /* TypeReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */; }; 6CE2AC122BD43FB900416A02 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE2AC112BD43FB900416A02 /* SignInView.swift */; }; 6CE2AC1B2BD444BB00416A02 /* OpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 6CE2AC1A2BD444BB00416A02 /* OpenAI */; settings = {ATTRIBUTES = (Required, ); }; }; 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */; }; @@ -127,6 +130,9 @@ 6CDB29FA2BAA07B10081037B /* GPTChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTChatViewModel.swift; sourceTree = ""; }; 6CDB29FC2BAA07FD0081037B /* GPTChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTChatView.swift; sourceTree = ""; }; 6CDB29FE2BAA08280081037B /* GPTChatListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPTChatListViewModel.swift; sourceTree = ""; }; + 6CE1030B2BD56A4000498AA4 /* TypeReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportView.swift; sourceTree = ""; }; + 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportModel.swift; sourceTree = ""; }; + 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportViewModel.swift; sourceTree = ""; }; 6CE2AC112BD43FB900416A02 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; 6CF130AE2BAB0C4F00A437B6 /* AuthenticatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedView.swift; sourceTree = ""; }; @@ -293,6 +299,7 @@ 6C7704882B722647001B17CB /* View */ = { isa = PBXGroup; children = ( + 6CE103092BD56A2B00498AA4 /* Report */, 357666112BBD5494002C226A /* Splah */, 6CF130AB2BAB0C2D00A437B6 /* Authentication */, 6CE2AC102BD43FA800416A02 /* SignIn */, @@ -441,6 +448,24 @@ path = ChatGPT; sourceTree = ""; }; + 6CE103092BD56A2B00498AA4 /* Report */ = { + isa = PBXGroup; + children = ( + 6CE1030A2BD56A3200498AA4 /* Type */, + ); + path = Report; + sourceTree = ""; + }; + 6CE1030A2BD56A3200498AA4 /* Type */ = { + isa = PBXGroup; + children = ( + 6CE1030B2BD56A4000498AA4 /* TypeReportView.swift */, + 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */, + 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */, + ); + path = Type; + sourceTree = ""; + }; 6CE2AC102BD43FA800416A02 /* SignIn */ = { isa = PBXGroup; children = ( @@ -606,6 +631,7 @@ files = ( 6C454A882B9DB6C2006FD9D0 /* CustomNavigationBar.swift in Sources */, 6CF130BF2BAB783300A437B6 /* APIConstants.swift in Sources */, + 6CE103102BD56A5B00498AA4 /* TypeReportViewModel.swift in Sources */, 6CF130C52BAB79DE00A437B6 /* RollTheDiceAPI.swift in Sources */, 6C454A7A2B9DA67C006FD9D0 /* SignUpViewModel.swift in Sources */, 6C3237AA2B7C381500B699AB /* NewsView.swift in Sources */, @@ -619,6 +645,7 @@ 6C3237AC2B7C382200B699AB /* News.swift in Sources */, 3576661B2BBD65C3002C226A /* FieldStatisticsReportView.swift in Sources */, 357666152BBD5C04002C226A /* FieldStatisticsView.swift in Sources */, + 6CE1030E2BD56A5200498AA4 /* TypeReportModel.swift in Sources */, 357FC6EA2BCE866B00AD8915 /* DetailCardNews.swift in Sources */, 6CC4DDC92B5574670080E7E8 /* ContentView.swift in Sources */, 6C3237A52B7C37D100B699AB /* BookmarkView.swift in Sources */, @@ -648,6 +675,7 @@ 6C7704992B722A20001B17CB /* MainTabViewModel.swift in Sources */, 6C454A7C2B9DA71C006FD9D0 /* SignUpView.swift in Sources */, 6CF130C72BAB7B9800A437B6 /* RollTheDiceAPINews.swift in Sources */, + 6CE1030C2BD56A4000498AA4 /* TypeReportView.swift in Sources */, 6C77049D2B722CE0001B17CB /* BookmarkListView.swift in Sources */, 6C77049B2B722A5A001B17CB /* TabType.swift in Sources */, ); diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift new file mode 100644 index 00000000..e429c173 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift @@ -0,0 +1,21 @@ +// +// TypeReport.swift +// RollTheDice +// +// Created by Subeen on 4/21/24. +// + +import Foundation + +struct TypeReport { + var typeReportList: [[NewsType : Double]] +} + +enum NewsType { + case politics // 정치 + case economy // 경제 + case society // 사회 + case living // 생활/문화 + case world // 세계 + case science // IT/과학 +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift new file mode 100644 index 00000000..6b21b14e --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift @@ -0,0 +1,96 @@ +// +// TypeReportView.swift +// RollTheDice +// +// Created by Subeen on 4/21/24. +// + +import SwiftUI + +struct TypeReportView: View { + var body: some View { + ZStack { + Color.backgroundDark.ignoresSafeArea(.all) + + VStack { + CustomNavigationBar(title: "분야별 레포트", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) + + Spacer() + + HStack(spacing: 70) { + statisticsView + reportView + } + .frame(height: 500) + .padding(.horizontal, 110) + + Spacer() + } + + } + } + + var statisticsView: some View { + RoundedRectangle(cornerRadius: 16) + .foregroundStyle(.gray02) + } + + // TODO : 배치 바꾸기!! + /// 상단 막대 + 분야별 뉴스 통계 박스 + 통계 글 + 캐릭터 + /// 하단 막대 + /// 각각 묶어서 ZStack에 배치하기 + var reportView: some View { + RoundedRectangle(cornerRadius: 16) + .foregroundStyle(.gray02) + .overlay { + ZStack { + VStack { + Rectangle() + .frame(height: 1) + + + Rectangle() + .stroke(lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + .frame(height: 52) + .overlay { + Text("분야별 뉴스 통계") + .font(.pretendardBold24) + .foregroundStyle(.gray06) + } + .padding(.vertical, 20) + + + HStack { + Text("안녕하세요. 이것은 통계입니다. 안녕하세요. 이것은 통계입니다. ") + .multilineTextAlignment(.leading) + Spacer() + } + .padding(.horizontal, 4) + + Spacer() + + Rectangle() + .frame(height: 1) + } + .padding(.vertical, 34) + .padding(.horizontal, 24) + + HStack { + Spacer() + VStack { + Spacer() + Image(.scoopGray04) + .aspectRatio(0.5, contentMode: .fill) + .frame(height: 250, alignment: .top) + .clipped() + } +// Spacer() + } + } + } + } +} + +#Preview { + TypeReportView() +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift new file mode 100644 index 00000000..6c77c825 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift @@ -0,0 +1,27 @@ +// +// TypeReportViewModel.swift +// RollTheDice +// +// Created by Subeen on 4/21/24. +// + +import Foundation + +class TypeReportViewModel: ObservableObject { + @Published var typeReportList: TypeReport + + init( + typeReportList: TypeReport = .init( + typeReportList: [ + [NewsType.economy : 10.0], + [NewsType.living : 20.0], + [NewsType.politics : 30.0], + [NewsType.science : 5.0], + [NewsType.society : 5.0], + [NewsType.world : 30.0], + ] + ) + ) { + self.typeReportList = typeReportList + } +} From 20472bad9453d276ea253f6866310c51b83514dc Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 00:43:35 +0900 Subject: [PATCH 02/60] =?UTF-8?q?feat:=20=EC=9A=94=EC=9D=BC=EB=B3=84=20?= =?UTF-8?q?=EB=89=B4=EC=8A=A4=20=EA=B4=80=EB=9E=8C=20=EA=B0=9C=EC=88=98=20?= =?UTF-8?q?=ED=86=B5=EA=B3=84=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice.xcodeproj/project.pbxproj | 12 +++ .../View/Report/Daily/DailyReportView.swift | 97 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index 0f78e940..cc017c1a 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 6CE1030C2BD56A4000498AA4 /* TypeReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030B2BD56A4000498AA4 /* TypeReportView.swift */; }; 6CE1030E2BD56A5200498AA4 /* TypeReportModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */; }; 6CE103102BD56A5B00498AA4 /* TypeReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */; }; + 6CE103132BD56B1200498AA4 /* DailyReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE103122BD56B1200498AA4 /* DailyReportView.swift */; }; 6CE2AC122BD43FB900416A02 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE2AC112BD43FB900416A02 /* SignInView.swift */; }; 6CE2AC1B2BD444BB00416A02 /* OpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 6CE2AC1A2BD444BB00416A02 /* OpenAI */; settings = {ATTRIBUTES = (Required, ); }; }; 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */; }; @@ -133,6 +134,7 @@ 6CE1030B2BD56A4000498AA4 /* TypeReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportView.swift; sourceTree = ""; }; 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportModel.swift; sourceTree = ""; }; 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportViewModel.swift; sourceTree = ""; }; + 6CE103122BD56B1200498AA4 /* DailyReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyReportView.swift; sourceTree = ""; }; 6CE2AC112BD43FB900416A02 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; 6CF130AE2BAB0C4F00A437B6 /* AuthenticatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedView.swift; sourceTree = ""; }; @@ -451,6 +453,7 @@ 6CE103092BD56A2B00498AA4 /* Report */ = { isa = PBXGroup; children = ( + 6CE103112BD56AF700498AA4 /* Daily */, 6CE1030A2BD56A3200498AA4 /* Type */, ); path = Report; @@ -466,6 +469,14 @@ path = Type; sourceTree = ""; }; + 6CE103112BD56AF700498AA4 /* Daily */ = { + isa = PBXGroup; + children = ( + 6CE103122BD56B1200498AA4 /* DailyReportView.swift */, + ); + path = Daily; + sourceTree = ""; + }; 6CE2AC102BD43FA800416A02 /* SignIn */ = { isa = PBXGroup; children = ( @@ -633,6 +644,7 @@ 6CF130BF2BAB783300A437B6 /* APIConstants.swift in Sources */, 6CE103102BD56A5B00498AA4 /* TypeReportViewModel.swift in Sources */, 6CF130C52BAB79DE00A437B6 /* RollTheDiceAPI.swift in Sources */, + 6CE103132BD56B1200498AA4 /* DailyReportView.swift in Sources */, 6C454A7A2B9DA67C006FD9D0 /* SignUpViewModel.swift in Sources */, 6C3237AA2B7C381500B699AB /* NewsView.swift in Sources */, 6CF130C92BAB7CC200A437B6 /* BaseTargetType.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift new file mode 100644 index 00000000..85998ecf --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift @@ -0,0 +1,97 @@ +// +// DailyReportView.swift +// RollTheDice +// +// Created by Subeen on 4/22/24. +// + +import SwiftUI + + +struct DailyReportView: View { + var body: some View { + ZStack { + Color.backgroundDark.ignoresSafeArea(.all) + + VStack { + CustomNavigationBar(title: "요일별 뉴스 관람 개수 통계", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) + + Spacer() + + HStack(spacing: 70) { + statisticsView + reportView + } + .frame(height: 500) + .padding(.horizontal, 110) + + Spacer() + } + + } + } + + var statisticsView: some View { + RoundedRectangle(cornerRadius: 16) + .foregroundStyle(.gray02) + } + + // TODO : 배치 바꾸기!! + /// 상단 막대 + 분야별 뉴스 통계 박스 + 통계 글 + 캐릭터 + /// 하단 막대 + /// 각각 묶어서 ZStack에 배치하기 + var reportView: some View { + RoundedRectangle(cornerRadius: 16) + .foregroundStyle(.gray02) + .overlay { + ZStack { + VStack { + Rectangle() + .frame(height: 1) + + + Rectangle() + .stroke(lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + .frame(height: 52) + .overlay { + Text("요일별 뉴스 관람 개수 통계") + .font(.pretendardBold24) + .foregroundStyle(.gray06) + } + .padding(.vertical, 20) + + + HStack { + Text("안녕하세요. 이것은 통계입니다. 안녕하세요. 이것은 통계입니다. ") + .multilineTextAlignment(.leading) + Spacer() + } + .padding(.horizontal, 4) + + Spacer() + + Rectangle() + .frame(height: 1) + } + .padding(.vertical, 34) + .padding(.horizontal, 24) + + HStack { + Spacer() + VStack { + Spacer() + Image(.scoopGray04) + .aspectRatio(0.5, contentMode: .fill) + .frame(height: 250, alignment: .top) + .clipped() + } +// Spacer() + } + } + } + } +} + +#Preview { + DailyReportView() +} From 5cca59d5cdf58f2b2ee227184f2547f87e6283cd Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 00:45:21 +0900 Subject: [PATCH 03/60] =?UTF-8?q?chore:=20=ED=86=B5=EA=B3=84=20=EB=B7=B0?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EB=82=B4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20path=20type=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift index 7cd3d0f5..7f51d3ca 100644 --- a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift +++ b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift @@ -9,4 +9,7 @@ import Foundation enum PathType: Hashable { case chatView(isAiMode: Bool) + case detailNewsView + case typeReportView // 분야별 뉴스 통계 + case dailyReportView // 요일별 뉴스 관람 개수 통계 } From 0ebde20423c0d507600401772f9cdaaa46de3423 Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 01:15:54 +0900 Subject: [PATCH 04/60] =?UTF-8?q?feat:=20=EC=9D=BC=EB=B3=84=20=EB=89=B4?= =?UTF-8?q?=EC=8A=A4=20=EA=B4=80=EB=9E=8C=20=ED=86=B5=EA=B3=84=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=ED=94=84=20UI=20=EA=B5=AC=ED=98=84.=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 아직 수정할 부분 많음. --- .../RollTheDice.xcodeproj/project.pbxproj | 4 + .../View/Report/Daily/DailyBarChartView.swift | 135 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index cc017c1a..1fd1d0e3 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ 6CE1030E2BD56A5200498AA4 /* TypeReportModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */; }; 6CE103102BD56A5B00498AA4 /* TypeReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */; }; 6CE103132BD56B1200498AA4 /* DailyReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE103122BD56B1200498AA4 /* DailyReportView.swift */; }; + 6CE103152BD56CA800498AA4 /* DailyBarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE103142BD56CA800498AA4 /* DailyBarChartView.swift */; }; 6CE2AC122BD43FB900416A02 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE2AC112BD43FB900416A02 /* SignInView.swift */; }; 6CE2AC1B2BD444BB00416A02 /* OpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 6CE2AC1A2BD444BB00416A02 /* OpenAI */; settings = {ATTRIBUTES = (Required, ); }; }; 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */; }; @@ -135,6 +136,7 @@ 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportModel.swift; sourceTree = ""; }; 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportViewModel.swift; sourceTree = ""; }; 6CE103122BD56B1200498AA4 /* DailyReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyReportView.swift; sourceTree = ""; }; + 6CE103142BD56CA800498AA4 /* DailyBarChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyBarChartView.swift; sourceTree = ""; }; 6CE2AC112BD43FB900416A02 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; 6CF130AE2BAB0C4F00A437B6 /* AuthenticatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedView.swift; sourceTree = ""; }; @@ -473,6 +475,7 @@ isa = PBXGroup; children = ( 6CE103122BD56B1200498AA4 /* DailyReportView.swift */, + 6CE103142BD56CA800498AA4 /* DailyBarChartView.swift */, ); path = Daily; sourceTree = ""; @@ -681,6 +684,7 @@ 6C454A7E2B9DAA3F006FD9D0 /* SignUpFinishView.swift in Sources */, 6C7704A12B722CEB001B17CB /* ProfileView.swift in Sources */, 6C3237B72B7C434600B699AB /* ChatType.swift in Sources */, + 6CE103152BD56CA800498AA4 /* DailyBarChartView.swift in Sources */, 6CE2AC122BD43FB900416A02 /* SignInView.swift in Sources */, 6CC4DDC72B5574670080E7E8 /* RollTheDiceApp.swift in Sources */, 6C77048C2B722686001B17CB /* MainTabView.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift new file mode 100644 index 00000000..62b7b0e4 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -0,0 +1,135 @@ +// +// DailyBarChartView.swift +// RollTheDice +// +// Created by Subeen on 4/22/24. +// + + +import SwiftUI +import Charts + +struct DailyBarChartView: View { + + @State var selectedDay: Date? + @State var selectedView: Int? + + var previews: [DailyViews] = [ + .init(dateStr: "1월 1일", views: 32), + .init(dateStr: "1월 2일", views: 2), + .init(dateStr: "1월 3일", views: 300), + .init(dateStr: "1월 4일", views: 999), + .init(dateStr: "1월 5일", views: 12), + .init(dateStr: "1월 6일", views: 1), + .init(dateStr: "1월 7일", views: 99), + ] + + // TODO: 평균 기사 개수 구하는 함수 작성 + var avg: Double = 99.9 + + var body: some View { + VStack(alignment: .leading) { + HStack { + VStack(alignment: .leading) { + Text("이번주 평균") + .foregroundStyle(.gray04) + .font(.pretendardBold12) + HStack { + Text("\(avg)") + .foregroundStyle(.basicWhite) + .font(.pretendardBold40) + Text("기사") + .foregroundStyle(.gray04) + .font(.pretendardBold12) + } + } + Spacer() + } + + Chart{ + ForEach(previews) { day in + BarMark( + x: .value("Day", day.date, unit: .day), + y: .value("Views", day.views) + + ) + .foregroundStyle(.orange) + } + + if let selectedView = selectedView { + RuleMark( +// x: .value("Day", selectedDay!) + y: .value("Views", selectedView) + ) + .annotation( + position: .top, + alignment: .leading, + overflowResolution: .init( + x: .fit(to: .chart), + y: .disabled + ) + ) { +// Text("\(previews[selectedDay])") + Text("\(selectedView)") + } + } + } + + .padding(.horizontal, 20) + .chartXAxis { + AxisMarks(values: .stride(by: .day)) + } +// .chartYAxis { +// AxisMarks(preset: .extended, position: .leading) +// } + .chartYAxis(.hidden) + .chartXSelection(value: $selectedDay) + .chartYSelection(value: $selectedView) +// .chartOverlay { proxy in +// GeometryReader { geometry in +// Rectangle().fill(.clear).contentShape(Rectangle()) +// .onTapGesture { value in +//// let posX = +// } +// .gesture( +// DragGesture() +// .onChanged { value in +// let origin = geometry[proxy.plotFrame!].origin +// let location = CGPoint( +// x: value.location.x - origin.x, +// y: value.location.y - origin.y +// ) +// +// selectedDay = proxy.value(atX: location.x, as: Date.self)! +// let (date, view) = proxy.value(at: location, as: (Date, Int).self)! +// print("Location \(date), \(view)") +// } +// ) +// +// } +// } + } + .frame(width: 440, height: 300) + } +} + + +struct DailyViews: Identifiable { + let id = UUID() + let dateStr: String // DateFormatter로 변환 + let views: Int + + var date: Date { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "MM월 dd일" + + let convertedDate = dateFormatter.date(from: dateStr)! + + return convertedDate + } +} + + +#Preview { + DailyBarChartView() +} From 01fbbf5aaae7260d5a5f5450f9c2480ec579af36 Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 01:16:10 +0900 Subject: [PATCH 05/60] =?UTF-8?q?feat:=20=ED=86=B5=EA=B3=84=20=EA=B7=B8?= =?UTF-8?q?=EB=9E=98=ED=94=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/View/Report/Daily/DailyReportView.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift index 85998ecf..d425f4c1 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift @@ -18,7 +18,7 @@ struct DailyReportView: View { Spacer() - HStack(spacing: 70) { + HStack { statisticsView reportView } @@ -31,9 +31,15 @@ struct DailyReportView: View { } } + + /// 데일리 막대 그래프 var statisticsView: some View { RoundedRectangle(cornerRadius: 16) - .foregroundStyle(.gray02) + .stroke(.basicWhite, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + .background(.gray07) + .overlay { + DailyBarChartView() + } } // TODO : 배치 바꾸기!! @@ -64,6 +70,7 @@ struct DailyReportView: View { HStack { Text("안녕하세요. 이것은 통계입니다. 안녕하세요. 이것은 통계입니다. ") .multilineTextAlignment(.leading) + Spacer() } .padding(.horizontal, 4) @@ -75,6 +82,7 @@ struct DailyReportView: View { } .padding(.vertical, 34) .padding(.horizontal, 24) + .foregroundStyle(.gray06) HStack { Spacer() @@ -85,7 +93,6 @@ struct DailyReportView: View { .frame(height: 250, alignment: .top) .clipped() } -// Spacer() } } } From 4553f9a20a01b2448aecb337814e1fe3def9434d Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 01:16:25 +0900 Subject: [PATCH 06/60] =?UTF-8?q?chore:=20=EB=82=B4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20path=20type=EB=B3=84=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift index 20d1db1f..865ab725 100644 --- a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift +++ b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift @@ -31,6 +31,12 @@ struct RollTheDiceApp: App { case .chatView(isAiMode: false): Text("user") .navigationBarBackButtonHidden() + case .detailNewsView: + DetailCardNews() + case .typeReportView: + TypeReportView() + case .dailyReportView: + DailyReportView() } }) } From 082239d51dc33b83ebdd6072a52a24723577bcf9 Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 01:26:08 +0900 Subject: [PATCH 07/60] =?UTF-8?q?chore:=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/View/Report/Type/TypeReportView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift index 6b21b14e..98550acd 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift @@ -17,7 +17,7 @@ struct TypeReportView: View { Spacer() - HStack(spacing: 70) { + HStack { statisticsView reportView } @@ -32,7 +32,8 @@ struct TypeReportView: View { var statisticsView: some View { RoundedRectangle(cornerRadius: 16) - .foregroundStyle(.gray02) + .stroke(.basicWhite, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + .background(.gray07) } // TODO : 배치 바꾸기!! @@ -74,6 +75,7 @@ struct TypeReportView: View { } .padding(.vertical, 34) .padding(.horizontal, 24) + .foregroundStyle(.gray06) HStack { Spacer() From 2c393bd0eec34dba1b7c2297d20850094ec1c6eb Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 01:32:03 +0900 Subject: [PATCH 08/60] =?UTF-8?q?chore:=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice.xcodeproj/project.pbxproj | 44 ++-------------- .../Source/View/MainTab/MainTabView.swift | 2 +- .../ReportListView.swift} | 8 +-- .../DailyStatistics/DailyStatisticsView.swift | 47 ----------------- .../FieldStatisticsReportView.swift | 52 ------------------- .../FieldStatistics/FieldStatisticsView.swift | 50 ------------------ 6 files changed, 9 insertions(+), 194 deletions(-) rename iOS/RollTheDice/RollTheDice/Source/View/{Statistics/StatisticsListView.swift => Report/ReportListView.swift} (91%) delete mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Statistics/DailyStatistics/DailyStatisticsView.swift delete mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsReportView.swift delete mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsView.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index 1fd1d0e3..7f82900a 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -7,11 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 357666102BBD4BF6002C226A /* StatisticsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3576660F2BBD4BF6002C226A /* StatisticsListView.swift */; }; + 357666102BBD4BF6002C226A /* ReportListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3576660F2BBD4BF6002C226A /* ReportListView.swift */; }; 357666132BBD54AA002C226A /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357666122BBD54AA002C226A /* SplashView.swift */; }; - 357666152BBD5C04002C226A /* FieldStatisticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357666142BBD5C04002C226A /* FieldStatisticsView.swift */; }; - 357666172BBD5C5B002C226A /* DailyStatisticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357666162BBD5C5B002C226A /* DailyStatisticsView.swift */; }; - 3576661B2BBD65C3002C226A /* FieldStatisticsReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3576661A2BBD65C3002C226A /* FieldStatisticsReportView.swift */; }; 357FC6EA2BCE866B00AD8915 /* DetailCardNews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 357FC6E92BCE866B00AD8915 /* DetailCardNews.swift */; }; 35C71BF22B79F39900F777D1 /* ExyteChat in Frameworks */ = {isa = PBXBuildFile; productRef = 35C71BF12B79F39900F777D1 /* ExyteChat */; }; 6C32379F2B7C376D00B699AB /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C32379E2B7C376D00B699AB /* Bookmark.swift */; }; @@ -80,11 +77,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 3576660F2BBD4BF6002C226A /* StatisticsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsListView.swift; sourceTree = ""; }; + 3576660F2BBD4BF6002C226A /* ReportListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportListView.swift; sourceTree = ""; }; 357666122BBD54AA002C226A /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; - 357666142BBD5C04002C226A /* FieldStatisticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldStatisticsView.swift; sourceTree = ""; }; - 357666162BBD5C5B002C226A /* DailyStatisticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyStatisticsView.swift; sourceTree = ""; }; - 3576661A2BBD65C3002C226A /* FieldStatisticsReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldStatisticsReportView.swift; sourceTree = ""; }; 357FC6E92BCE866B00AD8915 /* DetailCardNews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailCardNews.swift; sourceTree = ""; }; 6C32379E2B7C376D00B699AB /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = ""; }; 6C3237A02B7C377600B699AB /* BookmarkViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkViewModel.swift; sourceTree = ""; }; @@ -165,16 +159,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 3576660D2BBD4A09002C226A /* Statistics */ = { - isa = PBXGroup; - children = ( - 3576660F2BBD4BF6002C226A /* StatisticsListView.swift */, - 357666182BBD6540002C226A /* FieldStatistics */, - 357666192BBD655A002C226A /* DailyStatistics */, - ); - path = Statistics; - sourceTree = ""; - }; 357666112BBD5494002C226A /* Splah */ = { isa = PBXGroup; children = ( @@ -183,23 +167,6 @@ path = Splah; sourceTree = ""; }; - 357666182BBD6540002C226A /* FieldStatistics */ = { - isa = PBXGroup; - children = ( - 357666142BBD5C04002C226A /* FieldStatisticsView.swift */, - 3576661A2BBD65C3002C226A /* FieldStatisticsReportView.swift */, - ); - path = FieldStatistics; - sourceTree = ""; - }; - 357666192BBD655A002C226A /* DailyStatistics */ = { - isa = PBXGroup; - children = ( - 357666162BBD5C5B002C226A /* DailyStatisticsView.swift */, - ); - path = DailyStatistics; - sourceTree = ""; - }; 6C32379D2B7C374E00B699AB /* BookmarkCard */ = { isa = PBXGroup; children = ( @@ -309,7 +276,6 @@ 6CE2AC102BD43FA800416A02 /* SignIn */, 6C454A762B9DA62C006FD9D0 /* SignUp */, 6C77048A2B72267E001B17CB /* MainTab */, - 3576660D2BBD4A09002C226A /* Statistics */, 6C77048D2B7229A3001B17CB /* News */, 6C7704902B7229B6001B17CB /* Debate */, 6C7704932B7229C4001B17CB /* Bookmark */, @@ -455,6 +421,7 @@ 6CE103092BD56A2B00498AA4 /* Report */ = { isa = PBXGroup; children = ( + 3576660F2BBD4BF6002C226A /* ReportListView.swift */, 6CE103112BD56AF700498AA4 /* Daily */, 6CE1030A2BD56A3200498AA4 /* Type */, ); @@ -655,11 +622,9 @@ 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */, 6CDB29F92BAA07350081037B /* GPTChat.swift in Sources */, 6CDB29FD2BAA07FD0081037B /* GPTChatView.swift in Sources */, - 357666102BBD4BF6002C226A /* StatisticsListView.swift in Sources */, + 357666102BBD4BF6002C226A /* ReportListView.swift in Sources */, 6C3237A12B7C377600B699AB /* BookmarkViewModel.swift in Sources */, 6C3237AC2B7C382200B699AB /* News.swift in Sources */, - 3576661B2BBD65C3002C226A /* FieldStatisticsReportView.swift in Sources */, - 357666152BBD5C04002C226A /* FieldStatisticsView.swift in Sources */, 6CE1030E2BD56A5200498AA4 /* TypeReportModel.swift in Sources */, 357FC6EA2BCE866B00AD8915 /* DetailCardNews.swift in Sources */, 6CC4DDC92B5574670080E7E8 /* ContentView.swift in Sources */, @@ -676,7 +641,6 @@ 6CF130B22BAB74BA00A437B6 /* NewsService.swift in Sources */, 6C3237B22B7C385000B699AB /* NewsListViewModel.swift in Sources */, 6C454A782B9DA657006FD9D0 /* SignUpQuestionView.swift in Sources */, - 357666172BBD5C5B002C226A /* DailyStatisticsView.swift in Sources */, 6CA901962BA2EC0100E20259 /* Font.swift in Sources */, 6CF130C22BAB786600A437B6 /* APIHeaderManager.swift in Sources */, 6CDB29FB2BAA07B10081037B /* GPTChatViewModel.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift index 6c9d2d26..923a8971 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift @@ -21,7 +21,7 @@ struct MainTabView: View { TabView(selection: $mainTabViewModel.selectedTabItem) { - StatisticsListView() + ReportListView() .tabItem { Image(systemName: "list.bullet.rectangle") } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/StatisticsListView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/ReportListView.swift similarity index 91% rename from iOS/RollTheDice/RollTheDice/Source/View/Statistics/StatisticsListView.swift rename to iOS/RollTheDice/RollTheDice/Source/View/Report/ReportListView.swift index 179b5975..0f298953 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/StatisticsListView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/ReportListView.swift @@ -8,7 +8,7 @@ import SwiftUI -struct StatisticsListView: View { +struct ReportListView: View { @State private var selectedSegment = 0 @@ -41,9 +41,9 @@ struct StatisticsListView: View { Spacer() if selectedSegment == 0 { - FieldStatisticsListView() + TypeReportView() } else if selectedSegment == 1 { - DailyStatisticsView() + DailyReportView() } @@ -58,5 +58,5 @@ struct StatisticsListView: View { } #Preview { - StatisticsListView() + ReportListView() } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/DailyStatistics/DailyStatisticsView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Statistics/DailyStatistics/DailyStatisticsView.swift deleted file mode 100644 index e8f1e3c6..00000000 --- a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/DailyStatistics/DailyStatisticsView.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// DailyStatisticsView.swift -// RollTheDice -// -// Created by 신예진 on 4/3/24. -// - -import Foundation -import SwiftUI - -struct DailyStatisticsView: View { - - var body: some View { - ZStack{ - Color.backgroundDark - .ignoresSafeArea(.all) - - ScrollView(.horizontal){ - - HStack{ - - VStack{ - Image("graph2") - - Spacer() - .frame(height: 50) - - Text("뉴스관람빈도") - .bold() - .font(.system(size: 20)) - } - - Spacer() - .frame(width: 100) - - } - - } - - } - } - -} - -#Preview { - DailyStatisticsView() -} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsReportView.swift deleted file mode 100644 index b45cafec..00000000 --- a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsReportView.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// FieldStatisticsReportView.swift -// RollTheDice -// -// Created by 신예진 on 4/3/24. -// - -import SwiftUI - -struct FieldStatisticsListView: View { - - var body: some View { - ZStack { - Rectangle() - .fill(Color.white) - .frame(width: 1000, height: 500) - .cornerRadius(10) - .shadow(radius: 5) - - VStack { - - Image("graph1") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 500, height: 500) - - HStack{ - Text("텍스트 예시") - .font(.title) - .foregroundColor(.black) - - } - - } - .padding() - - Spacer() - - - - - - - } - } -} - -struct FieldStatisticsListView_Previews: PreviewProvider { - static var previews: some View { - FieldStatisticsListView() - } -} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsView.swift deleted file mode 100644 index c5a6c2e6..00000000 --- a/iOS/RollTheDice/RollTheDice/Source/View/Statistics/FieldStatistics/FieldStatisticsView.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// FieldStatisticsView.swift -// RollTheDice -// -// Created by 신예진 on 4/3/24. -// - - -import SwiftUI - -struct FieldStatisticsView: View { - - var body: some View { - ZStack{ - Color.backgroundDark - .ignoresSafeArea(.all) - - ScrollView(.horizontal){ - - HStack{ - - VStack{ - Image("graph1") - - Spacer() - .frame(height: 50) - - Text("유저관심사반영") - .bold() - .font(.system(size: 20)) - } - - Spacer() - .frame(width: 100) - - } - - - - } - - } - - } - -} - -#Preview { - FieldStatisticsView() -} From 6aa846fe635740733451d29838a7af5dcfa420cc Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 01:32:30 +0900 Subject: [PATCH 09/60] =?UTF-8?q?chore:=20=EB=A0=88=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=EB=B7=B0=20=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98?= =?UTF-8?q?=EB=B0=94=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice/Source/View/Report/Daily/DailyReportView.swift | 2 +- .../RollTheDice/Source/View/Report/Type/TypeReportView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift index d425f4c1..3bbf9246 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift @@ -14,7 +14,7 @@ struct DailyReportView: View { Color.backgroundDark.ignoresSafeArea(.all) VStack { - CustomNavigationBar(title: "요일별 뉴스 관람 개수 통계", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) +// CustomNavigationBar(title: "요일별 뉴스 관람 개수 통계", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) Spacer() diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift index 98550acd..edb012e1 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift @@ -13,7 +13,7 @@ struct TypeReportView: View { Color.backgroundDark.ignoresSafeArea(.all) VStack { - CustomNavigationBar(title: "분야별 레포트", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) +// CustomNavigationBar(title: "분야별 레포트", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) Spacer() From 4561663130b618eb5607b59ad9162cfe354b9079 Mon Sep 17 00:00:00 2001 From: realhsb Date: Mon, 22 Apr 2024 02:17:36 +0900 Subject: [PATCH 10/60] =?UTF-8?q?feat:=20=ED=99=94=EB=A9=B4=20=EC=9A=94?= =?UTF-8?q?=EC=95=BD=20=EB=B7=B0=20=EB=B0=B0=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice.xcodeproj/project.pbxproj | 12 ++++ .../Debate/Summary/DebateSummaryView.swift | 61 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index 7f82900a..9c887cb2 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 6CE103102BD56A5B00498AA4 /* TypeReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */; }; 6CE103132BD56B1200498AA4 /* DailyReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE103122BD56B1200498AA4 /* DailyReportView.swift */; }; 6CE103152BD56CA800498AA4 /* DailyBarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE103142BD56CA800498AA4 /* DailyBarChartView.swift */; }; + 6CE1031A2BD57A2500498AA4 /* DebateSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE103192BD57A2500498AA4 /* DebateSummaryView.swift */; }; 6CE2AC122BD43FB900416A02 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE2AC112BD43FB900416A02 /* SignInView.swift */; }; 6CE2AC1B2BD444BB00416A02 /* OpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 6CE2AC1A2BD444BB00416A02 /* OpenAI */; settings = {ATTRIBUTES = (Required, ); }; }; 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */; }; @@ -131,6 +132,7 @@ 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeReportViewModel.swift; sourceTree = ""; }; 6CE103122BD56B1200498AA4 /* DailyReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyReportView.swift; sourceTree = ""; }; 6CE103142BD56CA800498AA4 /* DailyBarChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyBarChartView.swift; sourceTree = ""; }; + 6CE103192BD57A2500498AA4 /* DebateSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebateSummaryView.swift; sourceTree = ""; }; 6CE2AC112BD43FB900416A02 /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; 6CF130AC2BAB0C4400A437B6 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = ""; }; 6CF130AE2BAB0C4F00A437B6 /* AuthenticatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticatedView.swift; sourceTree = ""; }; @@ -305,6 +307,7 @@ 6C7704902B7229B6001B17CB /* Debate */ = { isa = PBXGroup; children = ( + 6CE103182BD57A1600498AA4 /* Summary */, 6CDB29F72BAA06FB0081037B /* ChatGPT */, 6C3237B32B7C433000B699AB /* ChatType */, ); @@ -447,6 +450,14 @@ path = Daily; sourceTree = ""; }; + 6CE103182BD57A1600498AA4 /* Summary */ = { + isa = PBXGroup; + children = ( + 6CE103192BD57A2500498AA4 /* DebateSummaryView.swift */, + ); + path = Summary; + sourceTree = ""; + }; 6CE2AC102BD43FA800416A02 /* SignIn */ = { isa = PBXGroup; children = ( @@ -655,6 +666,7 @@ 6C7704992B722A20001B17CB /* MainTabViewModel.swift in Sources */, 6C454A7C2B9DA71C006FD9D0 /* SignUpView.swift in Sources */, 6CF130C72BAB7B9800A437B6 /* RollTheDiceAPINews.swift in Sources */, + 6CE1031A2BD57A2500498AA4 /* DebateSummaryView.swift in Sources */, 6CE1030C2BD56A4000498AA4 /* TypeReportView.swift in Sources */, 6C77049D2B722CE0001B17CB /* BookmarkListView.swift in Sources */, 6C77049B2B722A5A001B17CB /* TabType.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift new file mode 100644 index 00000000..a96e30c9 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift @@ -0,0 +1,61 @@ +// +// DebateSummaryView.swift +// RollTheDice +// +// Created by Subeen on 4/22/24. +// + +import SwiftUI + +struct DebateSummaryView: View { + var body: some View { + ZStack { + Color.backgroundDark.ignoresSafeArea(.all) + + VStack { + CustomNavigationBar(title: "토론요약", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) + + sumView + + } + } + } + + var sumView: some View { + HStack(spacing: 0) { + Rectangle() + .foregroundStyle( + LinearGradient(colors: [.gray02, .gray02, .gray04], startPoint: .leading, endPoint: .trailing) + + ) + .frame(width: 88) + .clipShape(UnevenRoundedRectangle(topLeadingRadius: 20, topTrailingRadius: 20)) + + ZStack { + + UnevenRoundedRectangle(topLeadingRadius: 20, topTrailingRadius: 20) + .offset(x: 30, y: 20) + .foregroundStyle(.gray05) + .padding(.bottom, 20) + Rectangle() + .foregroundStyle( + LinearGradient(colors: [.gray03, .gray02], startPoint: .leading, endPoint: .trailing) + + ) + .clipShape(UnevenRoundedRectangle(topLeadingRadius: 20, topTrailingRadius: 20)) + .overlay { + UnevenRoundedRectangle(topLeadingRadius: 20, topTrailingRadius: 20) + .stroke(.gray07, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + } + + + } + } + .padding(.top, 60) + .padding(.horizontal, 110) + } +} + +#Preview { + DebateSummaryView() +} From 10b9d7d1a2bb23ad107ecd414f56bef71a1994d5 Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 17:16:18 +0900 Subject: [PATCH 11/60] =?UTF-8?q?chore:=20PathType=20(=EB=B6=81=EB=A7=88?= =?UTF-8?q?=ED=81=AC,=20=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift index 7f51d3ca..dda206b7 100644 --- a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift +++ b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift @@ -12,4 +12,6 @@ enum PathType: Hashable { case detailNewsView case typeReportView // 분야별 뉴스 통계 case dailyReportView // 요일별 뉴스 관람 개수 통계 + case bookmarkView // 북마크뷰 + case mypageView // 마이페이지뷰 } From 15e4d4865c787165c306a4b33b1f849ae4da12aa Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 17:17:01 +0900 Subject: [PATCH 12/60] =?UTF-8?q?chore:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=BB=A4=EC=8A=A4=ED=85=80=EB=82=B4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Bookmark/BookmarkListView.swift | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift index f652615a..ad0be59d 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Bookmark/BookmarkListView.swift @@ -8,43 +8,87 @@ import SwiftUI struct BookmarkListView: View { - @EnvironmentObject var bookmarkListViewModel : BookmarkListViewModel + @EnvironmentObject var pathModel: PathModel + @StateObject var bookmarkListViewModel : BookmarkListViewModel @State var selectedIndex: Int = 0 + var columns: [GridItem] = [ GridItem(), GridItem()] + var body: some View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) - BookmarkListContentView() - .padding(.leading, 20) + ZStack { + bookmarkListView +// BookmarkListContentView() + .padding(.leading, 20) + VStack(spacing: 0) { + + CustomNavigationBar( + title: "북마크", + isDisplayLeadingBtn: true, + leadingItems: + [ + (Image(.chevronLeft), { pathModel.paths.popLast() }), + ] + ) + + Spacer() + + } + + Spacer() + } } + .navigationBarBackButtonHidden() } - private struct BookmarkListContentView: View { - @EnvironmentObject var bookmarkListViewModel: BookmarkListViewModel - var columns: [GridItem] = [ GridItem(), GridItem() ] + var bookmarkListView: some View { - fileprivate var body: some View { - ScrollViewReader { value in - ScrollView(.horizontal, showsIndicators: false) { - LazyHGrid(rows: columns, spacing: 10) { - ForEach(bookmarkListViewModel.bookmarkList, id: \.self) { bookmark in - BookmarkView(bookmark: bookmark) + + ScrollViewReader { value in + ScrollView(.horizontal, showsIndicators: false) { + LazyHGrid(rows: columns, spacing: 10) { + ForEach(bookmarkListViewModel.bookmarkList, id: \.self) { bookmark in + BookmarkView(bookmark: bookmark) // .onTapGesture { // withAnimation { // selectedIndex = index // value.scrollTo(index) // } // } - } } - .padding(.vertical, 90) } + .padding(.vertical, 90) } } } + +// private struct BookmarkListContentView: View { +// @StateObject var bookmarkListViewModel: BookmarkListViewModel +// var columns: [GridItem] = [ GridItem(), GridItem()] +// +// fileprivate var body: some View { +// ScrollViewReader { value in +// ScrollView(.horizontal, showsIndicators: false) { +// LazyHGrid(rows: columns, spacing: 10) { +// ForEach(bookmarkListViewModel.bookmarkList, id: \.self) { bookmark in +// BookmarkView(bookmark: bookmark) +//// .onTapGesture { +//// withAnimation { +//// selectedIndex = index +//// value.scrollTo(index) +//// } +//// } +// } +// } +// .padding(.vertical, 90) +// } +// } +// } +// } } #Preview { - BookmarkListView() + BookmarkListView(bookmarkListViewModel: BookmarkListViewModel()) .environmentObject(BookmarkListViewModel()) } From daf0cc39a26491ca7884a5aff4719e0a75df2876 Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 17:17:10 +0900 Subject: [PATCH 13/60] =?UTF-8?q?chore:=20=EB=A9=94=EC=9D=B8=ED=83=AD?= =?UTF-8?q?=EB=B7=B0=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EB=82=B4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/View/MainTab/MainTabView.swift | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift index 923a8971..bd9ca7d9 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift @@ -18,30 +18,39 @@ struct MainTabView: View { ZStack { Color.backgroundDark .ignoresSafeArea(.all) - - TabView(selection: $mainTabViewModel.selectedTabItem) { + VStack { + CustomNavigationBar( + isDisplayTrailingBtn: true, + trailingItems: [ + (Image(.bookmarkfill), { pathModel.paths.append(.bookmarkView)}), + (Image(.profileWhite), { pathModel.paths.append(.mypageView)}) + ] + ) - ReportListView() - .tabItem { - Image(systemName: "list.bullet.rectangle") - } - .tag(0) - - NewsListView(newsListViewModel: newsListViewModel) - .tabItem { - Image(systemName: "square.3.layers.3d.down.left") - } -// .environmentObject(newsListViewModel) - .tag(1) - - ChatTypeView() -// .environmentObject(pathModel) - .tabItem { - Image(systemName: "message") - } -// .environmentObject(pathModel) - .tag(2) - + TabView(selection: $mainTabViewModel.selectedTabItem) { + + ReportListView() + .tabItem { + Image(systemName: "list.bullet.rectangle") + } + .tag(0) + + NewsListView(newsListViewModel: newsListViewModel) + .tabItem { + Image(systemName: "square.3.layers.3d.down.left") + } + // .environmentObject(newsListViewModel) + .tag(1) + + ChatTypeView() + // .environmentObject(pathModel) + .tabItem { + Image(systemName: "message") + } + // .environmentObject(pathModel) + .tag(2) + + } } } } From d71365ce4c796f0d45f236820a4bf75d84844f74 Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 17:17:28 +0900 Subject: [PATCH 14/60] =?UTF-8?q?chore:=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=EB=B7=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift index 865ab725..28f6e117 100644 --- a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift +++ b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift @@ -14,6 +14,7 @@ struct RollTheDiceApp: App { @StateObject private var pathModel = PathModel() @StateObject var newsListViewModel = NewsListViewModel() + @StateObject var bookmarkListViewModel = BookmarkListViewModel() var body: some Scene { WindowGroup { @@ -37,11 +38,17 @@ struct RollTheDiceApp: App { TypeReportView() case .dailyReportView: DailyReportView() + case .bookmarkView: + BookmarkListView(bookmarkListViewModel: bookmarkListViewModel) + case .mypageView: + Text("mypageView") } }) } + .navigationBarBackButtonHidden() .environmentObject(pathModel) + } else { SignUpView() } From c3026cc213ba3f7115c80ea8152d2ef5966a0ddb Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 21:18:44 +0900 Subject: [PATCH 15/60] =?UTF-8?q?design:=20=ED=86=A0=EB=A1=A0=20=EC=9A=94?= =?UTF-8?q?=EC=95=BD=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Debate/Summary/DebateSummaryView.swift | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift index a96e30c9..b45259e1 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift @@ -26,7 +26,7 @@ struct DebateSummaryView: View { Rectangle() .foregroundStyle( LinearGradient(colors: [.gray02, .gray02, .gray04], startPoint: .leading, endPoint: .trailing) - + ) .frame(width: 88) .clipShape(UnevenRoundedRectangle(topLeadingRadius: 20, topTrailingRadius: 20)) @@ -39,16 +39,66 @@ struct DebateSummaryView: View { .padding(.bottom, 20) Rectangle() .foregroundStyle( - LinearGradient(colors: [.gray03, .gray02], startPoint: .leading, endPoint: .trailing) - + LinearGradient(colors: [.gray03, .gray02, .gray02, .gray02, .gray02, .gray02, .gray02], startPoint: .leading, endPoint: .trailing) + ) .clipShape(UnevenRoundedRectangle(topLeadingRadius: 20, topTrailingRadius: 20)) .overlay { UnevenRoundedRectangle(topLeadingRadius: 20, topTrailingRadius: 20) - .stroke(.gray07, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + .stroke(.gray07, lineWidth: 1.0) } - + + } + // TODO: 말풍선 수정하기 + .overlay { + VStack(spacing: 20) { + Rectangle() + .frame(height: 1) + + Text("“ 이번 토론의 핵심 내용을 알려줄게요! ”") + .font(.pretendardBold40) + .padding(.top, 40) + + HStack(alignment: .top, spacing: 0) { + UnevenRoundedRectangle(topLeadingRadius: 60, bottomLeadingRadius: 60, bottomTrailingRadius: 60) + .foregroundStyle(.gray03) + .overlay { + Text("요약") + + } + ZStack(alignment: .topLeading) { + Rectangle() + .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, height: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) + .foregroundStyle(.gray03) + Circle() + + .trim(from: 0.5, to: 1) + +// .trim(from: 0, to: 0.5) + .frame(width: 200, height: 200) +// .clipped() + .foregroundStyle(.gray02) + + } + + + Image(.scoopGray01) + .resizable() + .scaledToFit() + .frame(height: 400) + +// .frame(height: 350, alignment: .top) +// .clipped() + } + .padding(.horizontal, 40) + + Spacer() + Rectangle() + .frame(height: 1) + } + .padding(.horizontal, 40) + .padding(.vertical, 40) } } .padding(.top, 60) From b7905dc212ab7770b2b4da4573e7e074df4753ad Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 21:37:51 +0900 Subject: [PATCH 16/60] =?UTF-8?q?chore:=20=ED=86=A0=EB=A1=A0=20=EC=9A=94?= =?UTF-8?q?=EC=95=BD=20=EB=B7=B0=20path=20type=20=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?=EB=B0=8F=20=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift | 5 ++++- .../RollTheDice/Source/Model/Path/PathType.swift | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift index 28f6e117..1997d440 100644 --- a/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift +++ b/iOS/RollTheDice/RollTheDice/RollTheDiceApp.swift @@ -24,6 +24,7 @@ struct RollTheDiceApp: App { MainTabView(newsListViewModel: newsListViewModel) .navigationDestination(for: PathType.self, destination: { pathType in + // 각 뷰마다 .navigationBarBackButtonHidden() 설정하기! switch pathType { case .chatView(isAiMode: true) : GPTChatView() @@ -42,10 +43,12 @@ struct RollTheDiceApp: App { BookmarkListView(bookmarkListViewModel: bookmarkListViewModel) case .mypageView: Text("mypageView") + case .debateSummaryView: + DebateSummaryView() } }) } - .navigationBarBackButtonHidden() + .environmentObject(pathModel) diff --git a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift index dda206b7..2ec6fb10 100644 --- a/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift +++ b/iOS/RollTheDice/RollTheDice/Source/Model/Path/PathType.swift @@ -9,9 +9,13 @@ import Foundation enum PathType: Hashable { case chatView(isAiMode: Bool) - case detailNewsView + case detailNewsView // 뉴스 자세히 보기 + case typeReportView // 분야별 뉴스 통계 case dailyReportView // 요일별 뉴스 관람 개수 통계 + + case debateSummaryView // 토론 요약 페이지 뷰 + case bookmarkView // 북마크뷰 case mypageView // 마이페이지뷰 } From 35d1690897931fe989f9383ee55ffe6ca6da0a38 Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 21:38:12 +0900 Subject: [PATCH 17/60] =?UTF-8?q?chore:=20=ED=86=A0=EB=A1=A0=20=EC=9A=94?= =?UTF-8?q?=EC=95=BD=20=EB=B7=B0=20=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/View/Debate/Summary/DebateSummaryView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift index b45259e1..8c17b97c 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift @@ -8,17 +8,20 @@ import SwiftUI struct DebateSummaryView: View { + @EnvironmentObject var pathModel: PathModel + var body: some View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) VStack { - CustomNavigationBar(title: "토론요약", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) + CustomNavigationBar(title: "토론요약", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), { pathModel.paths.popLast() })]) sumView } } + .navigationBarBackButtonHidden() } var sumView: some View { From 6c8d2a98530f92fe5d41f964854f68b7ecc30125 Mon Sep 17 00:00:00 2001 From: realhsb Date: Tue, 23 Apr 2024 21:38:49 +0900 Subject: [PATCH 18/60] =?UTF-8?q?design:=20=ED=86=A0=EB=A1=A0=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B7=B0=20UI=20=EC=9E=AC=EB=94=94=EC=9E=90=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Debate/ChatType/ChatTypeView.swift | 143 ++++++++++++------ 1 file changed, 98 insertions(+), 45 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift index 9c43fc90..f93b8c35 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift @@ -9,90 +9,143 @@ import SwiftUI struct ChatTypeView: View { @EnvironmentObject private var pathModel: PathModel - @State var isSelected: Bool = false +// @State var isSelected: Bool = false var body: some View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) - ChatTypeContentView(isSelected: $isSelected) + VStack { + CustomNavigationBar( + isDisplayTrailingBtn: true, + trailingItems: [ + (Image(.bookmarkfill), { pathModel.paths.append(.bookmarkView)}), + (Image(.profileWhite), { pathModel.paths.append(.mypageView)}) + ] + ) + Spacer() + recentReadNewsView + Spacer() + + chatListView + + + } + } } - private struct ChatTypeContentView: View { - - @Binding var isSelected: Bool - @EnvironmentObject private var pathModel: PathModel - - fileprivate var body: some View { + var recentReadNewsView: some View { + VStack(alignment: .leading) { + Text("최근에 본 뉴스") + .foregroundStyle(.gray01) + .font(.pretendardBold32) HStack { Button { - isSelected.toggle() - pathModel.paths.append(.chatView(isAiMode: true)) + // isSelected.toggle() + // pathModel.paths.append(.chatView(isAiMode: true)) } label: { RoundedRectangle(cornerRadius: 15) .foregroundStyle(.primary01) - .frame(width: 350, height: 400) - + .frame(width: 200, height: 200) + .overlay { VStack { HStack { - Text("Chat GPT랑\n토론하기") + Text("기사제목") .multilineTextAlignment(.leading) - .font(.system(size: 40, weight: .bold)) + .font(.pretendardBold24) .foregroundStyle(.basicWhite) - - Spacer() } - - Spacer() - + } + } + } + + Button { + // isSelected.toggle() + // pathModel.paths.append(.chatView(isAiMode: true)) + } label: { + RoundedRectangle(cornerRadius: 15) + .foregroundStyle(.primary01) + .frame(width: 200, height: 200) + + .overlay { + VStack { HStack { - Spacer() - Text("🤖") - .font(.system(size: 100)) + Text("기사제목") + .multilineTextAlignment(.leading) + .font(.pretendardBold24) + .foregroundStyle(.basicWhite) } } - .padding(.horizontal, 35) - .padding(.bottom, 35) - .padding(.top, 45) - - } } Button { - pathModel.paths.append(.chatView(isAiMode: false)) + // isSelected.toggle() + // pathModel.paths.append(.chatView(isAiMode: true)) } label: { RoundedRectangle(cornerRadius: 15) - .foregroundStyle(.gray01) - .frame(width: 350, height: 400) - + .foregroundStyle(.primary01) + .frame(width: 200, height: 200) + .overlay { VStack { HStack { - Spacer() - Text("인간이랑\n토론하기") - .multilineTextAlignment(.trailing) - .font(.system(size: 40, weight: .bold)) - .foregroundStyle(.primary01) + Text("기사제목") + .multilineTextAlignment(.leading) + .font(.pretendardBold24) + .foregroundStyle(.basicWhite) } - - Spacer() - + } + } + } + } + } + } + + var chatListView: some View { + VStack(alignment: .leading) { + + Text("채팅 목록") + .foregroundStyle(.gray01) + .font(.pretendardBold32) + + ScrollView { + HStack { + Button { + // isSelected.toggle() + // pathModel.paths.append(.chatView(isAiMode: true)) + } label: { + RoundedRectangle(cornerRadius: 15) + .foregroundStyle(.primary01) + .frame(width: 600, height: 100) + + .overlay { HStack { - Text("🫶") - .font(.system(size: 100)) + Text("기사제목") + .multilineTextAlignment(.leading) + .font(.pretendardBold24) + .foregroundStyle(.basicWhite) + Spacer() + + } } - .padding(.horizontal, 35) - .padding(.bottom, 35) - .padding(.top, 45) - } + } + Button { + pathModel.paths.append(.debateSummaryView) + } label: { + + Image(.chevronRight) + .padding(10) + .background(.basicBlack) + } } } } } + } #Preview { From aaafa27fdde0199dfc16195003e1fa74c2fc8a38 Mon Sep 17 00:00:00 2001 From: yeonjy <81320703+yeonjy@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:56:50 +0900 Subject: [PATCH 19/60] =?UTF-8?q?=08docs:=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 52000b23..8935c478 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,21 @@ --- -## 📁 개발 시스템 구조도 +## 📁 개발 시스템 구성도 ![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/5df840e4-9c04-4d01-8fe1-119f53d4ae12) +## 📙 시스템 디자인 +![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/62e40a3d-8143-4caf-aa55-1a73f7d97e90) + +![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/9694ef38-8112-4448-885c-9a0f49886eb5) | ![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/7cb02e75-bf3c-4387-bd5c-44d012575c9e) +---|---| + +![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/cece44e4-8a78-4b5b-877f-dc43597d4ecc) | ![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/af4b50a7-33e8-4563-ac8b-462c87a3b380) +---|---| + +![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/5cb15e5e-4f14-4f62-aad6-93d3de38305c) +---| + ## 🔗 ERD ![](https://github.com/tukcomCD2024/JinJiHan/assets/81320703/99ab2be8-b7a0-417e-8e94-6ec2f3f6b118) From efa9fa57130f20d10bf282bc7715b55db94a636a Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 18:55:35 +0900 Subject: [PATCH 20/60] =?UTF-8?q?feat:=20DailyReport=20=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice.xcodeproj/project.pbxproj | 8 ++++ .../View/Report/Daily/DailyReportModel.swift | 35 ++++++++++++++++++ .../Report/Daily/DailyReportViewModel.swift | 37 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index 9c887cb2..fdde27bb 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ 6C454A822B9DAFA3006FD9D0 /* Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A812B9DAFA3006FD9D0 /* Path.swift */; }; 6C454A842B9DAFCB006FD9D0 /* PathType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A832B9DAFCB006FD9D0 /* PathType.swift */; }; 6C454A882B9DB6C2006FD9D0 /* CustomNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A872B9DB6C2006FD9D0 /* CustomNavigationBar.swift */; }; + 6C4F7BAB2BDE50C600ED01DA /* DailyReportModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4F7BAA2BDE50C600ED01DA /* DailyReportModel.swift */; }; + 6C4F7BAD2BDE510900ED01DA /* DailyReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4F7BAC2BDE510900ED01DA /* DailyReportViewModel.swift */; }; 6C77048C2B722686001B17CB /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C77048B2B722686001B17CB /* MainTabView.swift */; }; 6C77048F2B7229B1001B17CB /* NewsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C77048E2B7229B1001B17CB /* NewsListView.swift */; }; 6C7704992B722A20001B17CB /* MainTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7704982B722A20001B17CB /* MainTabViewModel.swift */; }; @@ -98,6 +100,8 @@ 6C454A812B9DAFA3006FD9D0 /* Path.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Path.swift; sourceTree = ""; }; 6C454A832B9DAFCB006FD9D0 /* PathType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathType.swift; sourceTree = ""; }; 6C454A872B9DB6C2006FD9D0 /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = ""; }; + 6C4F7BAA2BDE50C600ED01DA /* DailyReportModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyReportModel.swift; sourceTree = ""; }; + 6C4F7BAC2BDE510900ED01DA /* DailyReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyReportViewModel.swift; sourceTree = ""; }; 6C77048B2B722686001B17CB /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 6C77048E2B7229B1001B17CB /* NewsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsListView.swift; sourceTree = ""; }; 6C7704982B722A20001B17CB /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = ""; }; @@ -444,6 +448,8 @@ 6CE103112BD56AF700498AA4 /* Daily */ = { isa = PBXGroup; children = ( + 6C4F7BAA2BDE50C600ED01DA /* DailyReportModel.swift */, + 6C4F7BAC2BDE510900ED01DA /* DailyReportViewModel.swift */, 6CE103122BD56B1200498AA4 /* DailyReportView.swift */, 6CE103142BD56CA800498AA4 /* DailyBarChartView.swift */, ); @@ -643,6 +649,7 @@ 6C3237AE2B7C382E00B699AB /* NewsViewModel.swift in Sources */, 6C77048F2B7229B1001B17CB /* NewsListView.swift in Sources */, 357666132BBD54AA002C226A /* SplashView.swift in Sources */, + 6C4F7BAD2BDE510900ED01DA /* DailyReportViewModel.swift in Sources */, 6C3237A72B7C37E500B699AB /* BookmarkListViewModel.swift in Sources */, 6C454A822B9DAFA3006FD9D0 /* Path.swift in Sources */, 6C94799E2BD3C00C00D5AEEB /* Image.swift in Sources */, @@ -658,6 +665,7 @@ 6C32379F2B7C376D00B699AB /* Bookmark.swift in Sources */, 6C454A7E2B9DAA3F006FD9D0 /* SignUpFinishView.swift in Sources */, 6C7704A12B722CEB001B17CB /* ProfileView.swift in Sources */, + 6C4F7BAB2BDE50C600ED01DA /* DailyReportModel.swift in Sources */, 6C3237B72B7C434600B699AB /* ChatType.swift in Sources */, 6CE103152BD56CA800498AA4 /* DailyBarChartView.swift in Sources */, 6CE2AC122BD43FB900416A02 /* SignInView.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift new file mode 100644 index 00000000..a7a30e44 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift @@ -0,0 +1,35 @@ +// +// DailyReportModel.swift +// RollTheDice +// +// Created by Subeen on 4/28/24. +// + +import Foundation +import SwiftUI + +struct DailyReport: Hashable, Identifiable { + let id = UUID() + let dateStr: String // DateFormatter로 변환 + let views: Int + + var date: Date { + // TODO: Format Style 사용해서 String -> Date 생성하기 +// let strategy = Date.ParseStrategy(format: "\(month: .twoDigits)년 \(month: .twoDigits)월 \(day: .defaultDigits)", timeZone: TimeZone(abbreviation: "CDT")!) + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy년 MM월 dd일" + + let convertedDate = dateFormatter.date(from: dateStr)! + + return convertedDate + } + + init( + dateStr: String, + views: Int + ) { + self.dateStr = dateStr + self.views = views + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift new file mode 100644 index 00000000..aebf139c --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift @@ -0,0 +1,37 @@ +// +// DailyReportViewModel.swift +// RollTheDice +// +// Created by Subeen on 4/28/24. +// + +import Foundation + +class DailyReportViewModel: ObservableObject{ + @Published var dailyViews: [DailyReport] + + /// 일주일 평균 조회수 + var averageView: String { + var aver = 0.0 + + for daily in dailyViews { + aver += Double(daily.views) + } + + return String(format: "%.1f", aver / 7) + } + + init( + dailyViews: [DailyReport] = [ + .init(dateStr: "2024년 1월 1일", views: 32), + .init(dateStr: "2024년 1월 2일", views: 2), + .init(dateStr: "2024년 1월 3일", views: 300), + .init(dateStr: "2024년 1월 4일", views: 999), + .init(dateStr: "2024년 1월 5일", views: 12), + .init(dateStr: "2024년 1월 6일", views: 1), + .init(dateStr: "2024년 1월 7일", views: 99), + ] + ) { + self.dailyViews = dailyViews + } +} From fe3923dc40905446c2d759c7092323bc5d02003a Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 18:55:57 +0900 Subject: [PATCH 21/60] =?UTF-8?q?design:=20=EC=9D=BC=EC=9D=BC=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EB=B7=B0=20UI=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Report/Daily/DailyBarChartView.swift | 92 ++++++------------- .../View/Report/Daily/DailyReportView.swift | 5 +- 2 files changed, 29 insertions(+), 68 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift index 62b7b0e4..c8f8f312 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -11,21 +11,26 @@ import Charts struct DailyBarChartView: View { + @StateObject var dailyViewModel: DailyReportViewModel + @State var selectedDay: Date? @State var selectedView: Int? - var previews: [DailyViews] = [ - .init(dateStr: "1월 1일", views: 32), - .init(dateStr: "1월 2일", views: 2), - .init(dateStr: "1월 3일", views: 300), - .init(dateStr: "1월 4일", views: 999), - .init(dateStr: "1월 5일", views: 12), - .init(dateStr: "1월 6일", views: 1), - .init(dateStr: "1월 7일", views: 99), - ] + + var dateToView: Int? { + if let selectedDay { + for preview in dailyViewModel.dailyViews { + print("\(preview.date.formatted()), \(selectedDay.formatted(date: .long, time: .omitted))") + if preview.date.formatted(date: .long, time: .omitted) == selectedDay.formatted(date: .long, time: .omitted) { + return preview.views + } + } + } + return nil + } // TODO: 평균 기사 개수 구하는 함수 작성 - var avg: Double = 99.9 +// var avg: Double = 99.9 var body: some View { VStack(alignment: .leading) { @@ -35,7 +40,7 @@ struct DailyBarChartView: View { .foregroundStyle(.gray04) .font(.pretendardBold12) HStack { - Text("\(avg)") + Text(dailyViewModel.averageView) .foregroundStyle(.basicWhite) .font(.pretendardBold40) Text("기사") @@ -47,19 +52,19 @@ struct DailyBarChartView: View { } Chart{ - ForEach(previews) { day in + ForEach(dailyViewModel.dailyViews) { day in BarMark( - x: .value("Day", day.date, unit: .day), + x: .value("Day", day.date, unit: .weekdayOrdinal), y: .value("Views", day.views) ) .foregroundStyle(.orange) } - if let selectedView = selectedView { + if let selectedDay = selectedDay { + RuleMark( -// x: .value("Day", selectedDay!) - y: .value("Views", selectedView) + x: .value("Day", selectedDay, unit: .day) ) .annotation( position: .top, @@ -69,67 +74,22 @@ struct DailyBarChartView: View { y: .disabled ) ) { -// Text("\(previews[selectedDay])") - Text("\(selectedView)") + + Text("\(dateToView ?? -1)") } + .foregroundStyle(.basicWhite) } } - - .padding(.horizontal, 20) .chartXAxis { - AxisMarks(values: .stride(by: .day)) + AxisMarks(values: .automatic(desiredCount: 7)) } -// .chartYAxis { -// AxisMarks(preset: .extended, position: .leading) -// } .chartYAxis(.hidden) .chartXSelection(value: $selectedDay) - .chartYSelection(value: $selectedView) -// .chartOverlay { proxy in -// GeometryReader { geometry in -// Rectangle().fill(.clear).contentShape(Rectangle()) -// .onTapGesture { value in -//// let posX = -// } -// .gesture( -// DragGesture() -// .onChanged { value in -// let origin = geometry[proxy.plotFrame!].origin -// let location = CGPoint( -// x: value.location.x - origin.x, -// y: value.location.y - origin.y -// ) -// -// selectedDay = proxy.value(atX: location.x, as: Date.self)! -// let (date, view) = proxy.value(at: location, as: (Date, Int).self)! -// print("Location \(date), \(view)") -// } -// ) -// -// } -// } } .frame(width: 440, height: 300) } } - -struct DailyViews: Identifiable { - let id = UUID() - let dateStr: String // DateFormatter로 변환 - let views: Int - - var date: Date { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "MM월 dd일" - - let convertedDate = dateFormatter.date(from: dateStr)! - - return convertedDate - } -} - - #Preview { - DailyBarChartView() + DailyBarChartView(dailyViewModel: DailyReportViewModel()) } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift index 3bbf9246..1e3eebaf 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift @@ -14,7 +14,7 @@ struct DailyReportView: View { Color.backgroundDark.ignoresSafeArea(.all) VStack { -// CustomNavigationBar(title: "요일별 뉴스 관람 개수 통계", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) + CustomNavigationBar( isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) Spacer() @@ -38,7 +38,8 @@ struct DailyReportView: View { .stroke(.basicWhite, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) .background(.gray07) .overlay { - DailyBarChartView() + DailyBarChartView(dailyViewModel: DailyReportViewModel()) + } } From c48c61af33d269d168809e6f66640e702b82ecef Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 19:20:07 +0900 Subject: [PATCH 22/60] =?UTF-8?q?feat:=20=EC=9D=BC=EC=9D=BC=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=EC=88=98=20?= =?UTF-8?q?=ED=8C=9D=EC=97=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Report/Daily/DailyBarChartView.swift | 58 +++++++++++++------ .../View/Report/Daily/DailyReportView.swift | 4 +- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift index c8f8f312..83b22bd6 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -17,21 +17,18 @@ struct DailyBarChartView: View { @State var selectedView: Int? - var dateToView: Int? { + var dateToValue: (date: Date, views: Int)? { if let selectedDay { for preview in dailyViewModel.dailyViews { print("\(preview.date.formatted()), \(selectedDay.formatted(date: .long, time: .omitted))") if preview.date.formatted(date: .long, time: .omitted) == selectedDay.formatted(date: .long, time: .omitted) { - return preview.views + return (selectedDay, preview.views) } } } return nil } - // TODO: 평균 기사 개수 구하는 함수 작성 -// var avg: Double = 99.9 - var body: some View { VStack(alignment: .leading) { HStack { @@ -51,6 +48,9 @@ struct DailyBarChartView: View { Spacer() } + Spacer() + .frame(height: 100) + Chart{ ForEach(dailyViewModel.dailyViews) { day in BarMark( @@ -62,22 +62,23 @@ struct DailyBarChartView: View { } if let selectedDay = selectedDay { + if dateToValue != nil { - RuleMark( - x: .value("Day", selectedDay, unit: .day) - ) - .annotation( - position: .top, - alignment: .leading, - overflowResolution: .init( - x: .fit(to: .chart), - y: .disabled + RuleMark( + x: .value("Day", selectedDay, unit: .day) ) - ) { - - Text("\(dateToView ?? -1)") + .annotation( + position: .top, + alignment: .centerLastTextBaseline, + overflowResolution: .init( + x: .fit(to: .chart), + y: .disabled + ) + ) { + popoverView + } + .foregroundStyle(.basicWhite) } - .foregroundStyle(.basicWhite) } } .chartXAxis { @@ -86,7 +87,26 @@ struct DailyBarChartView: View { .chartYAxis(.hidden) .chartXSelection(value: $selectedDay) } - .frame(width: 440, height: 300) + } + + @ViewBuilder + var popoverView: some View { + VStack(alignment: .center) { + Text("\(dateToValue?.date.formatted(date: .numeric, time: .omitted) ?? "")") + .font(.pretendardRegular14) + Text("\(dateToValue?.views ?? 0)") + .font(.pretendardBold32) + + } + .padding(10) + .background(.gray06) + .clipShape( + RoundedRectangle(cornerRadius: 8) + ) + .overlay { + RoundedRectangle(cornerRadius: 8) + .stroke(.gray05, lineWidth: 1.0) + } } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift index 1e3eebaf..b6b44bc7 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift @@ -20,7 +20,7 @@ struct DailyReportView: View { HStack { statisticsView - reportView + reportView } .frame(height: 500) .padding(.horizontal, 110) @@ -39,7 +39,7 @@ struct DailyReportView: View { .background(.gray07) .overlay { DailyBarChartView(dailyViewModel: DailyReportViewModel()) - + .padding(50) } } From fa3e8cc07d9e0d9206a9643510dfaeb449268553 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 20:30:28 +0900 Subject: [PATCH 23/60] =?UTF-8?q?design:=20=ED=8C=8C=EC=9D=B4=EC=B0=A8?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20Color=20Assets=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ColorAssets.xcassets/Report/Contents.json | 6 +++ .../Report/reportCyan.colorset/Contents.json | 38 +++++++++++++++++++ .../Report/reportGreen.colorset/Contents.json | 38 +++++++++++++++++++ .../Report/reportPink.colorset/Contents.json | 38 +++++++++++++++++++ .../reportPurple.colorset/Contents.json | 38 +++++++++++++++++++ .../reportYellow.colorset/Contents.json | 38 +++++++++++++++++++ 6 files changed, 196 insertions(+) create mode 100644 iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/Contents.json create mode 100644 iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportCyan.colorset/Contents.json create mode 100644 iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportGreen.colorset/Contents.json create mode 100644 iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPink.colorset/Contents.json create mode 100644 iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPurple.colorset/Contents.json create mode 100644 iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportYellow.colorset/Contents.json diff --git a/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/Contents.json b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportCyan.colorset/Contents.json b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportCyan.colorset/Contents.json new file mode 100644 index 00000000..37a19a22 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportCyan.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD8", + "green" : "0xD0", + "red" : "0x23" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD8", + "green" : "0xD0", + "red" : "0x23" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportGreen.colorset/Contents.json b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportGreen.colorset/Contents.json new file mode 100644 index 00000000..0aeb53c7 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportGreen.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xDE", + "red" : "0x6F" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xDE", + "red" : "0x6F" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPink.colorset/Contents.json b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPink.colorset/Contents.json new file mode 100644 index 00000000..67f870da --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x73", + "green" : "0x47", + "red" : "0xF8" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x73", + "green" : "0x47", + "red" : "0xF8" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPurple.colorset/Contents.json b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPurple.colorset/Contents.json new file mode 100644 index 00000000..fb5a073e --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportPurple.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x2B", + "red" : "0xC8" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x2B", + "red" : "0xC8" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportYellow.colorset/Contents.json b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportYellow.colorset/Contents.json new file mode 100644 index 00000000..d6fb2665 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Resources/ColorAssets.xcassets/Report/reportYellow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x17", + "green" : "0xCC", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x17", + "green" : "0xCC", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} From ae8c3174769e79af75b5d11f9b947df63e217df7 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 20:32:04 +0900 Subject: [PATCH 24/60] =?UTF-8?q?chore:=20NewsType=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice.xcodeproj/project.pbxproj | 12 +++++ .../Source/Model/Report/NewsType.swift | 52 +++++++++++++++++++ .../View/Report/Type/TypeReportModel.swift | 22 ++++---- 3 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index fdde27bb..2b922267 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 6C3237B22B7C385000B699AB /* NewsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237B12B7C385000B699AB /* NewsListViewModel.swift */; }; 6C3237B52B7C433D00B699AB /* ChatTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237B42B7C433D00B699AB /* ChatTypeView.swift */; }; 6C3237B72B7C434600B699AB /* ChatType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237B62B7C434600B699AB /* ChatType.swift */; }; + 6C41B8D22BDE696200274FA4 /* NewsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C41B8D12BDE696200274FA4 /* NewsType.swift */; }; 6C454A782B9DA657006FD9D0 /* SignUpQuestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A772B9DA657006FD9D0 /* SignUpQuestionView.swift */; }; 6C454A7A2B9DA67C006FD9D0 /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A792B9DA67C006FD9D0 /* SignUpViewModel.swift */; }; 6C454A7C2B9DA71C006FD9D0 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A7B2B9DA71C006FD9D0 /* SignUpView.swift */; }; @@ -93,6 +94,7 @@ 6C3237B12B7C385000B699AB /* NewsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsListViewModel.swift; sourceTree = ""; }; 6C3237B42B7C433D00B699AB /* ChatTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTypeView.swift; sourceTree = ""; }; 6C3237B62B7C434600B699AB /* ChatType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatType.swift; sourceTree = ""; }; + 6C41B8D12BDE696200274FA4 /* NewsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsType.swift; sourceTree = ""; }; 6C454A772B9DA657006FD9D0 /* SignUpQuestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpQuestionView.swift; sourceTree = ""; }; 6C454A792B9DA67C006FD9D0 /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; }; 6C454A7B2B9DA71C006FD9D0 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; }; @@ -206,6 +208,14 @@ path = ChatType; sourceTree = ""; }; + 6C41B8D02BDE695A00274FA4 /* Report */ = { + isa = PBXGroup; + children = ( + 6C41B8D12BDE696200274FA4 /* NewsType.swift */, + ); + path = Report; + sourceTree = ""; + }; 6C454A762B9DA62C006FD9D0 /* SignUp */ = { isa = PBXGroup; children = ( @@ -220,6 +230,7 @@ 6C454A7F2B9DAF21006FD9D0 /* Model */ = { isa = PBXGroup; children = ( + 6C41B8D02BDE695A00274FA4 /* Report */, 6C454A802B9DAF9F006FD9D0 /* Path */, ); path = Model; @@ -639,6 +650,7 @@ 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */, 6CDB29F92BAA07350081037B /* GPTChat.swift in Sources */, 6CDB29FD2BAA07FD0081037B /* GPTChatView.swift in Sources */, + 6C41B8D22BDE696200274FA4 /* NewsType.swift in Sources */, 357666102BBD4BF6002C226A /* ReportListView.swift in Sources */, 6C3237A12B7C377600B699AB /* BookmarkViewModel.swift in Sources */, 6C3237AC2B7C382200B699AB /* News.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift b/iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift new file mode 100644 index 00000000..99deabbe --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/Model/Report/NewsType.swift @@ -0,0 +1,52 @@ +// +// NewsReport.swift +// RollTheDice +// +// Created by Subeen on 4/28/24. +// + +import Foundation +import SwiftUI + +enum NewsType { + case politics // 정치 + case economy // 경제 + case society // 사회 + case living // 생활/문화 + case world // 세계 + case science // IT/과학 + + var desciption: String { + switch self { + case .politics: + "정치" + case .economy: + "경제" + case .society: + "사회" + case .living: + "생활/문화" + case .world: + "세계" + case .science: + "IT/과학" + } + } + + var color: SwiftUI.Color { + switch self { + case .politics: + Color.reportCyan + case .economy: + Color.reportGreen + case .society: + Color.reportPurple + case .living: + Color.reportYellow + case .world: + Color.primary01 + case .science: + Color.reportPink + } + } +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift index e429c173..ead8dbd6 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift @@ -7,15 +7,19 @@ import Foundation -struct TypeReport { - var typeReportList: [[NewsType : Double]] +struct TypeReportList { + var reportList: [TypeReport] } -enum NewsType { - case politics // 정치 - case economy // 경제 - case society // 사회 - case living // 생활/문화 - case world // 세계 - case science // IT/과학 +struct TypeReport: Hashable, Identifiable { + var id = UUID() + var typeReportList: NewsType + var view: Int + + // case politics // 정치 + // case economy // 경제 + // case society // 사회 + // case living // 생활/문화 + // case world // 세계 + // case science // IT/과학 } From d7dfef40b1bc7abb50a719e220f436f376688693 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 20:33:15 +0900 Subject: [PATCH 25/60] =?UTF-8?q?chore:=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Report/Daily/DailyBarChartView.swift | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift index 83b22bd6..124f4634 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -17,10 +17,9 @@ struct DailyBarChartView: View { @State var selectedView: Int? - var dateToValue: (date: Date, views: Int)? { + var selectedValue: (date: Date, views: Int)? { if let selectedDay { for preview in dailyViewModel.dailyViews { - print("\(preview.date.formatted()), \(selectedDay.formatted(date: .long, time: .omitted))") if preview.date.formatted(date: .long, time: .omitted) == selectedDay.formatted(date: .long, time: .omitted) { return (selectedDay, preview.views) } @@ -58,15 +57,20 @@ struct DailyBarChartView: View { y: .value("Views", day.views) ) - .foregroundStyle(.orange) + .cornerRadius(8) + .foregroundStyle(.primary01.gradient) +// .foregroundStyle(selectedValue?.date.formatted(date: .abbreviated, time: .omitted) == selectedDay?.formatted(date: .abbreviated, time: .omitted) ? .orange : .blue) + //TODO: 바 선택 / 미선택에 따른 막대 투명도 조절 + .opacity(selectedValue?.date == nil || selectedValue?.date.formatted(date: .numeric, time: .omitted) == selectedDay?.formatted(date: .numeric, time: .omitted) ? 1 : 0.5) } if let selectedDay = selectedDay { - if dateToValue != nil { + if selectedValue != nil { RuleMark( x: .value("Day", selectedDay, unit: .day) ) + .zIndex(-1) .annotation( position: .top, alignment: .centerLastTextBaseline, @@ -92,14 +96,14 @@ struct DailyBarChartView: View { @ViewBuilder var popoverView: some View { VStack(alignment: .center) { - Text("\(dateToValue?.date.formatted(date: .numeric, time: .omitted) ?? "")") + Text("\(selectedValue?.date.formatted(date: .numeric, time: .omitted) ?? "")") .font(.pretendardRegular14) - Text("\(dateToValue?.views ?? 0)") - .font(.pretendardBold32) + Text("\(selectedValue?.views ?? 0)") + .font(.pretendardBold24) } .padding(10) - .background(.gray06) + .background(.gray06.gradient) .clipShape( RoundedRectangle(cornerRadius: 8) ) From 92ff4a993ac14679b57f02d6ad0ab24bffabe4a7 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 20:37:33 +0900 Subject: [PATCH 26/60] =?UTF-8?q?chore:=20Report=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Report/Daily/DailyBarChartView.swift | 4 +-- .../View/Report/Daily/DailyReportModel.swift | 12 +++------ .../Report/Daily/DailyReportViewModel.swift | 26 ++++++++++--------- .../Report/Type/TypeReportViewModel.swift | 24 ++++++++++------- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift index 124f4634..58ac3055 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -19,7 +19,7 @@ struct DailyBarChartView: View { var selectedValue: (date: Date, views: Int)? { if let selectedDay { - for preview in dailyViewModel.dailyViews { + for preview in dailyViewModel.dailyReportList.reportList { if preview.date.formatted(date: .long, time: .omitted) == selectedDay.formatted(date: .long, time: .omitted) { return (selectedDay, preview.views) } @@ -51,7 +51,7 @@ struct DailyBarChartView: View { .frame(height: 100) Chart{ - ForEach(dailyViewModel.dailyViews) { day in + ForEach(dailyViewModel.dailyReportList.reportList) { day in BarMark( x: .value("Day", day.date, unit: .weekdayOrdinal), y: .value("Views", day.views) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift index a7a30e44..9a5051cc 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportModel.swift @@ -8,6 +8,10 @@ import Foundation import SwiftUI +struct DailyReportList: Hashable { + var reportList: [DailyReport] +} + struct DailyReport: Hashable, Identifiable { let id = UUID() let dateStr: String // DateFormatter로 변환 @@ -24,12 +28,4 @@ struct DailyReport: Hashable, Identifiable { return convertedDate } - - init( - dateStr: String, - views: Int - ) { - self.dateStr = dateStr - self.views = views - } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift index aebf139c..344ca3bb 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportViewModel.swift @@ -8,13 +8,13 @@ import Foundation class DailyReportViewModel: ObservableObject{ - @Published var dailyViews: [DailyReport] + @Published var dailyReportList: DailyReportList /// 일주일 평균 조회수 var averageView: String { var aver = 0.0 - for daily in dailyViews { + for daily in dailyReportList.reportList { aver += Double(daily.views) } @@ -22,16 +22,18 @@ class DailyReportViewModel: ObservableObject{ } init( - dailyViews: [DailyReport] = [ - .init(dateStr: "2024년 1월 1일", views: 32), - .init(dateStr: "2024년 1월 2일", views: 2), - .init(dateStr: "2024년 1월 3일", views: 300), - .init(dateStr: "2024년 1월 4일", views: 999), - .init(dateStr: "2024년 1월 5일", views: 12), - .init(dateStr: "2024년 1월 6일", views: 1), - .init(dateStr: "2024년 1월 7일", views: 99), - ] + dailyReportList: DailyReportList = .init( + reportList: + [.init(dateStr: "2024년 1월 1일", views: 32), + .init(dateStr: "2024년 1월 2일", views: 2), + .init(dateStr: "2024년 1월 3일", views: 300), + .init(dateStr: "2024년 1월 4일", views: 999), + .init(dateStr: "2024년 1월 5일", views: 12), + .init(dateStr: "2024년 1월 6일", views: 1), + .init(dateStr: "2024년 1월 7일", views: 99), + ] + ) ) { - self.dailyViews = dailyViews + self.dailyReportList = dailyReportList } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift index 6c77c825..987ac463 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift @@ -8,20 +8,26 @@ import Foundation class TypeReportViewModel: ObservableObject { - @Published var typeReportList: TypeReport + + @Published var typeReportList: TypeReportList init( - typeReportList: TypeReport = .init( - typeReportList: [ - [NewsType.economy : 10.0], - [NewsType.living : 20.0], - [NewsType.politics : 30.0], - [NewsType.science : 5.0], - [NewsType.society : 5.0], - [NewsType.world : 30.0], + typeReportList: TypeReportList = .init( + reportList: [ + .init(typeReportList: .economy, view: 10), + .init(typeReportList: .living, view: 20), + .init(typeReportList: .politics, view: 30), + .init(typeReportList: .science, view: 5), + .init(typeReportList: .society, view: 5), + .init(typeReportList: .world, view: 30) ] ) ) { self.typeReportList = typeReportList } + + // 비율이 낮은순으로 정렬 (파이 차트에서 반시계방향으로 그래프 차지) + var sortedList: [TypeReport] { + return typeReportList.reportList.sorted { $0.view < $1.view } + } } From 7ccb5e0c2f26d091418e78215dfa92e6762f8a4d Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 20:37:57 +0900 Subject: [PATCH 27/60] =?UTF-8?q?feat:=20Report=20Navigation=20Path=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/View/Report/Daily/DailyReportView.swift | 6 +++++- .../Source/View/Report/Type/TypeReportView.swift | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift index b6b44bc7..ed73c2ad 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift @@ -9,12 +9,16 @@ import SwiftUI struct DailyReportView: View { + + @EnvironmentObject var pathModel: PathModel + var body: some View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) VStack { - CustomNavigationBar( isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) + /// 내비게이션 뒤로가기 + CustomNavigationBar(isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {pathModel.paths.popLast()})]) Spacer() diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift index edb012e1..3b8d3d27 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift @@ -8,12 +8,15 @@ import SwiftUI struct TypeReportView: View { + + @EnvironmentObject var pathModel: PathModel + var body: some View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) VStack { -// CustomNavigationBar(title: "분야별 레포트", isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {})]) + CustomNavigationBar(isDisplayLeadingBtn: true, leadingItems: [(Image(.chevronLeft), {pathModel.paths.popLast()})]) Spacer() From 2d7695f83ffaa8af9f2c75957199c09a02d25972 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 20:55:10 +0900 Subject: [PATCH 28/60] =?UTF-8?q?chore:=20TypeReport=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Report/Type/TypeReportModel.swift | 4 ++-- .../Report/Type/TypeReportViewModel.swift | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift index ead8dbd6..1c35be6e 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportModel.swift @@ -7,13 +7,13 @@ import Foundation -struct TypeReportList { +struct TypeReportList: Hashable { var reportList: [TypeReport] } struct TypeReport: Hashable, Identifiable { var id = UUID() - var typeReportList: NewsType + var newsType: NewsType var view: Int // case politics // 정치 diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift index 987ac463..d23bce97 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportViewModel.swift @@ -9,25 +9,25 @@ import Foundation class TypeReportViewModel: ObservableObject { - @Published var typeReportList: TypeReportList + @Published var reportList: TypeReportList init( - typeReportList: TypeReportList = .init( + reportList: TypeReportList = .init( reportList: [ - .init(typeReportList: .economy, view: 10), - .init(typeReportList: .living, view: 20), - .init(typeReportList: .politics, view: 30), - .init(typeReportList: .science, view: 5), - .init(typeReportList: .society, view: 5), - .init(typeReportList: .world, view: 30) + .init(newsType: .economy, view: 10), + .init(newsType: .living, view: 20), + .init(newsType: .politics, view: 30), + .init(newsType: .science, view: 5), + .init(newsType: .society, view: 5), + .init(newsType: .world, view: 30) ] ) ) { - self.typeReportList = typeReportList + self.reportList = reportList } - // 비율이 낮은순으로 정렬 (파이 차트에서 반시계방향으로 그래프 차지) + // 비율이 낮은 순으로 정렬 (파이 차트에서 반시계방향으로 그래프 차지) var sortedList: [TypeReport] { - return typeReportList.reportList.sorted { $0.view < $1.view } + return reportList.reportList.sorted { $0.view > $1.view } } } From e1c01ee08930c332fdaf344c9af15ddc0b487500 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 20:55:30 +0900 Subject: [PATCH 29/60] =?UTF-8?q?feat:=20Pie=20Chart=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice.xcodeproj/project.pbxproj | 4 +++ .../View/Report/Type/TypePieChartView.swift | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index 2b922267..f1ca2bac 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 6C3237B52B7C433D00B699AB /* ChatTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237B42B7C433D00B699AB /* ChatTypeView.swift */; }; 6C3237B72B7C434600B699AB /* ChatType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3237B62B7C434600B699AB /* ChatType.swift */; }; 6C41B8D22BDE696200274FA4 /* NewsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C41B8D12BDE696200274FA4 /* NewsType.swift */; }; + 6C41B8D42BDE6D2500274FA4 /* TypePieChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C41B8D32BDE6D2500274FA4 /* TypePieChartView.swift */; }; 6C454A782B9DA657006FD9D0 /* SignUpQuestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A772B9DA657006FD9D0 /* SignUpQuestionView.swift */; }; 6C454A7A2B9DA67C006FD9D0 /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A792B9DA67C006FD9D0 /* SignUpViewModel.swift */; }; 6C454A7C2B9DA71C006FD9D0 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C454A7B2B9DA71C006FD9D0 /* SignUpView.swift */; }; @@ -95,6 +96,7 @@ 6C3237B42B7C433D00B699AB /* ChatTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTypeView.swift; sourceTree = ""; }; 6C3237B62B7C434600B699AB /* ChatType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatType.swift; sourceTree = ""; }; 6C41B8D12BDE696200274FA4 /* NewsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsType.swift; sourceTree = ""; }; + 6C41B8D32BDE6D2500274FA4 /* TypePieChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypePieChartView.swift; sourceTree = ""; }; 6C454A772B9DA657006FD9D0 /* SignUpQuestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpQuestionView.swift; sourceTree = ""; }; 6C454A792B9DA67C006FD9D0 /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; }; 6C454A7B2B9DA71C006FD9D0 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; }; @@ -450,6 +452,7 @@ isa = PBXGroup; children = ( 6CE1030B2BD56A4000498AA4 /* TypeReportView.swift */, + 6C41B8D32BDE6D2500274FA4 /* TypePieChartView.swift */, 6CE1030D2BD56A5200498AA4 /* TypeReportModel.swift */, 6CE1030F2BD56A5B00498AA4 /* TypeReportViewModel.swift */, ); @@ -652,6 +655,7 @@ 6CDB29FD2BAA07FD0081037B /* GPTChatView.swift in Sources */, 6C41B8D22BDE696200274FA4 /* NewsType.swift in Sources */, 357666102BBD4BF6002C226A /* ReportListView.swift in Sources */, + 6C41B8D42BDE6D2500274FA4 /* TypePieChartView.swift in Sources */, 6C3237A12B7C377600B699AB /* BookmarkViewModel.swift in Sources */, 6C3237AC2B7C382200B699AB /* News.swift in Sources */, 6CE1030E2BD56A5200498AA4 /* TypeReportModel.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift new file mode 100644 index 00000000..5c667532 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift @@ -0,0 +1,30 @@ +// +// TypePieChartView.swift +// RollTheDice +// +// Created by Subeen on 4/28/24. +// + +import SwiftUI +import Charts + +struct TypePieChartView: View { + + @StateObject var reportViewModel: TypeReportViewModel + + var body: some View { + Chart(reportViewModel.sortedList) { report in + SectorMark( + angle: .value("Views", report.view), + innerRadius: .ratio(0.618), + angularInset: 2.0 + ) + .cornerRadius(8) + .foregroundStyle(report.newsType.color) + } + } +} + +#Preview { + TypePieChartView(reportViewModel: TypeReportViewModel()) +} From 0b79061abec135e524992c154361ef171d07ef4d Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 21:06:18 +0900 Subject: [PATCH 30/60] =?UTF-8?q?feat:=20Pie=20Chart=20=EA=B0=80=EC=9A=B4?= =?UTF-8?q?=EB=8D=B0=EC=97=90=20=EB=A7=8E=EC=9D=B4=20=EB=B3=B8=20=EB=B6=84?= =?UTF-8?q?=EC=95=BC=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EB=B0=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Report/Type/TypePieChartView.swift | 23 ++++++++++++++++++- .../View/Report/Type/TypeReportView.swift | 4 ++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift index 5c667532..9ff50a82 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift @@ -12,16 +12,37 @@ struct TypePieChartView: View { @StateObject var reportViewModel: TypeReportViewModel + var mostViewed: NewsType { + return reportViewModel.sortedList.first!.newsType + } + var body: some View { Chart(reportViewModel.sortedList) { report in SectorMark( angle: .value("Views", report.view), - innerRadius: .ratio(0.618), + innerRadius: .ratio(0.7), angularInset: 2.0 ) .cornerRadius(8) .foregroundStyle(report.newsType.color) } + /// pie chart의 가운데 문구 + .chartBackground { chartProxy in + GeometryReader { geometry in + let frame = geometry[chartProxy.plotFrame!] + + VStack { + Text("많이 본 분야") + .foregroundStyle(.gray05) + .font(.pretendardRegular14) + Text(mostViewed.desciption) + .foregroundStyle(.gray01) + .font(.pretendardBold24) + } + .position(x: frame.midX, y: frame.midY) + } + } + .padding(100) } } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift index 3b8d3d27..3f37a170 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift @@ -34,9 +34,13 @@ struct TypeReportView: View { } var statisticsView: some View { + RoundedRectangle(cornerRadius: 16) .stroke(.basicWhite, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) .background(.gray07) + .overlay { + TypePieChartView(reportViewModel: TypeReportViewModel()) + } } // TODO : 배치 바꾸기!! From d63e7b82c03e232a9124d833621f96d9bd0a66ff Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 21:37:34 +0900 Subject: [PATCH 31/60] =?UTF-8?q?chore:=20Report=20View=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=82=B4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=88=A8=EA=B9=80=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RollTheDice/Source/View/Report/Daily/DailyReportView.swift | 2 +- .../RollTheDice/Source/View/Report/Type/TypeReportView.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift index ed73c2ad..20b5db78 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyReportView.swift @@ -31,8 +31,8 @@ struct DailyReportView: View { Spacer() } - } + .navigationBarBackButtonHidden() } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift index 3f37a170..5f33b3bf 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypeReportView.swift @@ -31,6 +31,7 @@ struct TypeReportView: View { } } + .navigationBarBackButtonHidden() } var statisticsView: some View { From 95897a3af50b357b004544b5bbcbcbf542d180e9 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 21:38:42 +0900 Subject: [PATCH 32/60] =?UTF-8?q?feat:=20=ED=86=B5=EA=B3=84=20=ED=83=AD?= =?UTF-8?q?=EB=B7=B0=20UI=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/View/Report/ReportListView.swift | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/ReportListView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/ReportListView.swift index 0f298953..57cc844b 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/ReportListView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/ReportListView.swift @@ -10,6 +10,7 @@ import SwiftUI struct ReportListView: View { + @EnvironmentObject var pathModel: PathModel @State private var selectedSegment = 0 var body: some View { @@ -17,39 +18,34 @@ struct ReportListView: View { Color.backgroundDark .ignoresSafeArea(.all) - VStack { - HStack(spacing: 0) { - ForEach(0 ..< 2) { index in - Text(index == 0 ? "분야별 레포트" : "일별 레포트") - .bold() - .font(.system(size: 30)) - .frame(maxWidth: .infinity) - .padding() - .foregroundColor(selectedSegment == index ? .white : .primary01) - .background(selectedSegment == index ? Color.primary01 : .clear) - .cornerRadius(10) - .onTapGesture { - selectedSegment = index - } - .tag(index) - } + HStack { + Button { + pathModel.paths.append(.typeReportView) + } label: { + RoundedRectangle(cornerRadius: 8) + .stroke(.gray01, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + .background(.gray07) + .overlay { + TypePieChartView(reportViewModel: TypeReportViewModel(), isPreview: true) + } + .frame(width: 400, height: 400) + } - .padding(.leading, 200) - .padding(.trailing, 200) - .padding(.top,10) - Spacer() - - if selectedSegment == 0 { - TypeReportView() - } else if selectedSegment == 1 { - DailyReportView() + Button { + pathModel.paths.append(.dailyReportView) + } label: { + RoundedRectangle(cornerRadius: 8) + .stroke(.gray01, lineWidth: /*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) + .background(.gray07) + .overlay { + DailyBarChartView(dailyViewModel: DailyReportViewModel(), isPreview: true) + .frame(height: 200) + } + .frame(width: 400, height: 400) + } - - - Spacer() } - .padding() } From 92902fdd6d8629d9b9ec177fe556d8738f11fa00 Mon Sep 17 00:00:00 2001 From: realhsb Date: Sun, 28 Apr 2024 21:39:10 +0900 Subject: [PATCH 33/60] =?UTF-8?q?feat:=20=ED=94=84=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=EC=97=90=20=EB=94=B0=EB=A5=B8=20chart=20?= =?UTF-8?q?=ED=98=95=ED=83=9C=20=EB=B6=84=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Report/Daily/DailyBarChartView.swift | 68 +++++++++++-------- .../View/Report/Type/TypePieChartView.swift | 22 +++--- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift index 58ac3055..f1b36734 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -16,6 +16,8 @@ struct DailyBarChartView: View { @State var selectedDay: Date? @State var selectedView: Int? + var isPreview: Bool = false + var selectedValue: (date: Date, views: Int)? { if let selectedDay { @@ -30,25 +32,29 @@ struct DailyBarChartView: View { var body: some View { VStack(alignment: .leading) { - HStack { - VStack(alignment: .leading) { - Text("이번주 평균") - .foregroundStyle(.gray04) - .font(.pretendardBold12) - HStack { - Text(dailyViewModel.averageView) - .foregroundStyle(.basicWhite) - .font(.pretendardBold40) - Text("기사") + if !isPreview { + HStack { + VStack(alignment: .leading) { + Text("이번주 평균") .foregroundStyle(.gray04) .font(.pretendardBold12) + HStack { + Text(dailyViewModel.averageView) + .foregroundStyle(.basicWhite) + .font(.pretendardBold40) + Text("기사") + .foregroundStyle(.gray04) + .font(.pretendardBold12) + } } + Spacer() } + Spacer() + .frame(height: 100) } - Spacer() - .frame(height: 100) + Chart{ ForEach(dailyViewModel.dailyReportList.reportList) { day in @@ -59,32 +65,36 @@ struct DailyBarChartView: View { ) .cornerRadius(8) .foregroundStyle(.primary01.gradient) -// .foregroundStyle(selectedValue?.date.formatted(date: .abbreviated, time: .omitted) == selectedDay?.formatted(date: .abbreviated, time: .omitted) ? .orange : .blue) + //TODO: 바 선택 / 미선택에 따른 막대 투명도 조절 .opacity(selectedValue?.date == nil || selectedValue?.date.formatted(date: .numeric, time: .omitted) == selectedDay?.formatted(date: .numeric, time: .omitted) ? 1 : 0.5) } - if let selectedDay = selectedDay { - if selectedValue != nil { - - RuleMark( - x: .value("Day", selectedDay, unit: .day) - ) - .zIndex(-1) - .annotation( - position: .top, - alignment: .centerLastTextBaseline, - overflowResolution: .init( - x: .fit(to: .chart), - y: .disabled + if !isPreview { + if let selectedDay = selectedDay { + if selectedValue != nil { + + RuleMark( + x: .value("Day", selectedDay, unit: .day) ) - ) { - popoverView + .zIndex(-1) + .annotation( + position: .top, + alignment: .centerLastTextBaseline, + overflowResolution: .init( + x: .fit(to: .chart), + y: .disabled + ) + ) { + popoverView + } + .foregroundStyle(.basicWhite) } - .foregroundStyle(.basicWhite) } } } + .padding(isPreview ? 50 : 0) + .chartXAxis(isPreview ? .hidden : .visible) .chartXAxis { AxisMarks(values: .automatic(desiredCount: 7)) } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift index 9ff50a82..bbd26054 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Type/TypePieChartView.swift @@ -12,6 +12,8 @@ struct TypePieChartView: View { @StateObject var reportViewModel: TypeReportViewModel + var isPreview: Bool = false + var mostViewed: NewsType { return reportViewModel.sortedList.first!.newsType } @@ -24,22 +26,24 @@ struct TypePieChartView: View { angularInset: 2.0 ) .cornerRadius(8) - .foregroundStyle(report.newsType.color) + .foregroundStyle(report.newsType.color.gradient) } /// pie chart의 가운데 문구 .chartBackground { chartProxy in GeometryReader { geometry in let frame = geometry[chartProxy.plotFrame!] - VStack { - Text("많이 본 분야") - .foregroundStyle(.gray05) - .font(.pretendardRegular14) - Text(mostViewed.desciption) - .foregroundStyle(.gray01) - .font(.pretendardBold24) + if !isPreview { + VStack { + Text("많이 본 분야") + .foregroundStyle(.gray05) + .font(.pretendardRegular14) + Text(mostViewed.desciption) + .foregroundStyle(.gray01) + .font(.pretendardBold24) + } + .position(x: frame.midX, y: frame.midY) } - .position(x: frame.midX, y: frame.midY) } } .padding(100) From f65c266febfee5c028aee21aff4e907354a4586f Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 01:00:05 +0900 Subject: [PATCH 34/60] feat: MemberUpdateDto & MemberServiceDto --- .../domain/member/dto/MemberServiceDto.java | 14 ++++++++++ .../domain/member/dto/MemberUpdateDto.java | 26 +++++++++++++++++++ .../backend/domain/member/entity/Member.java | 8 +++--- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberServiceDto.java create mode 100644 backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberUpdateDto.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberServiceDto.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberServiceDto.java new file mode 100644 index 00000000..c61910fd --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberServiceDto.java @@ -0,0 +1,14 @@ +package com.rollthedice.backend.domain.member.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class MemberServiceDto { + private String email; + private String nickname; + private String imageUrl; +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberUpdateDto.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberUpdateDto.java new file mode 100644 index 00000000..8e0cd929 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/dto/MemberUpdateDto.java @@ -0,0 +1,26 @@ +package com.rollthedice.backend.domain.member.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor +@Schema(description = "멤버 업데이트 포맷") +public class MemberUpdateDto { + @Schema(description = "변경할 닉네임") + private String nickname; + @Schema(description = "변경할 이미지 S3 Url") + private String imageUrl; + + public MemberServiceDto toServiceDto(String email) { + return MemberServiceDto.builder() + .email(email) + .nickname(this.nickname) + .imageUrl(this.imageUrl) + .build(); + } +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java index 81c0d3e1..4efff30d 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java @@ -1,5 +1,6 @@ package com.rollthedice.backend.domain.member.entity; +import com.rollthedice.backend.domain.member.dto.MemberServiceDto; import com.rollthedice.backend.global.config.BaseTimeEntity; import jakarta.persistence.*; import lombok.*; @@ -29,9 +30,10 @@ public class Member extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Status status; - public Member update(String email, String imageUrl) { - this.email = email; - this.imageUrl = imageUrl; + public Member update(MemberServiceDto dto) { + this.email = dto.getEmail(); + this.imageUrl = dto.getImageUrl(); + this.nickname = dto.getNickname(); return this; } From bbffe993c3bb39363aceec19ef980357e299e1d2 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:27:12 +0900 Subject: [PATCH 35/60] refactor: jwt -> security directory --- .../jwt/exception/NotFoundTokenException.java | 2 +- .../JwtAuthenticationProcessingFilter.java | 73 +++++++++-------- .../jwt/refresh/domain/RefreshToken.java | 2 +- .../repository/RefreshTokenRepository.java | 4 +- .../refresh/service/RefreshTokenService.java | 8 +- .../jwt/service/JwtService.java | 81 +++++++++---------- .../{ => security}/jwt/util/PasswordUtil.java | 2 +- 7 files changed, 81 insertions(+), 91 deletions(-) rename backend/core/src/main/java/com/rollthedice/backend/global/{ => security}/jwt/exception/NotFoundTokenException.java (82%) rename backend/core/src/main/java/com/rollthedice/backend/global/{ => security}/jwt/filter/JwtAuthenticationProcessingFilter.java (54%) rename backend/core/src/main/java/com/rollthedice/backend/global/{ => security}/jwt/refresh/domain/RefreshToken.java (92%) rename backend/core/src/main/java/com/rollthedice/backend/global/{ => security}/jwt/refresh/repository/RefreshTokenRepository.java (61%) rename backend/core/src/main/java/com/rollthedice/backend/global/{ => security}/jwt/refresh/service/RefreshTokenService.java (76%) rename backend/core/src/main/java/com/rollthedice/backend/global/{ => security}/jwt/service/JwtService.java (64%) rename backend/core/src/main/java/com/rollthedice/backend/global/{ => security}/jwt/util/PasswordUtil.java (94%) diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/exception/NotFoundTokenException.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/NotFoundTokenException.java similarity index 82% rename from backend/core/src/main/java/com/rollthedice/backend/global/jwt/exception/NotFoundTokenException.java rename to backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/NotFoundTokenException.java index 658192ee..7ef6f50d 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/exception/NotFoundTokenException.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/NotFoundTokenException.java @@ -1,4 +1,4 @@ -package com.rollthedice.backend.global.jwt.exception; +package com.rollthedice.backend.global.security.jwt.exception; public class NotFoundTokenException extends RuntimeException { public NotFoundTokenException() { diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/filter/JwtAuthenticationProcessingFilter.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/filter/JwtAuthenticationProcessingFilter.java similarity index 54% rename from backend/core/src/main/java/com/rollthedice/backend/global/jwt/filter/JwtAuthenticationProcessingFilter.java rename to backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/filter/JwtAuthenticationProcessingFilter.java index e47cd54a..9a414821 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/filter/JwtAuthenticationProcessingFilter.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/filter/JwtAuthenticationProcessingFilter.java @@ -1,11 +1,12 @@ -package com.rollthedice.backend.global.jwt.filter; +package com.rollthedice.backend.global.security.jwt.filter; import com.rollthedice.backend.domain.member.entity.Member; import com.rollthedice.backend.domain.member.repository.MemberRepository; -import com.rollthedice.backend.global.jwt.refresh.domain.RefreshToken; -import com.rollthedice.backend.global.jwt.refresh.service.RefreshTokenService; -import com.rollthedice.backend.global.jwt.service.JwtService; -import com.rollthedice.backend.global.jwt.util.PasswordUtil; +import com.rollthedice.backend.global.security.jwt.exception.InvalidTokenException; +import com.rollthedice.backend.global.security.jwt.refresh.domain.RefreshToken; +import com.rollthedice.backend.global.security.jwt.refresh.service.RefreshTokenService; +import com.rollthedice.backend.global.security.jwt.service.JwtService; +import com.rollthedice.backend.global.security.jwt.util.PasswordUtil; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -29,8 +30,8 @@ public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter { private static final String NO_CHECK_URL = "/login"; private final JwtService jwtService; - private final RefreshTokenService refreshTokenService; private final MemberRepository memberRepository; + private final RefreshTokenService refreshTokenService; private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); @@ -47,6 +48,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (refreshToken != null) { checkRefreshTokenAndReIssueAccessToken(response, refreshToken); + response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } @@ -55,47 +57,44 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } public void checkRefreshTokenAndReIssueAccessToken(HttpServletResponse response, String refreshToken) { - RefreshToken refresh = refreshTokenService.findByToken(refreshToken); - String reIssuedRefreshToken = reIssueRefreshToken(refresh.getEmail()); - jwtService.sendAccessAndRefreshToken(response, - jwtService.createAccessToken(refresh.getEmail()), reIssuedRefreshToken); - } - - private String reIssueRefreshToken(String email) { - String reIssuedRefreshToken = jwtService.createRefreshToken(); - - refreshTokenService.updateToken(email, reIssuedRefreshToken); - return reIssuedRefreshToken; + if (jwtService.isTokenValid(refreshToken)) { + RefreshToken refresh = refreshTokenService.findByToken(refreshToken); + jwtService.sendAccessAndRefreshToken(response, refresh.getEmail()); + } } - private void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - jwtService.extractAccessToken(request) - .filter(jwtService::isTokenValid) - .ifPresent(accessToken -> jwtService.extractEmail(accessToken) - .ifPresent(email -> memberRepository.findByEmail(email) - .ifPresent(this::saveAuthentication))); + public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + log.info("checkAccessTokenAndAuthentication() 호출"); + try { + jwtService.extractAccessToken(request) + .ifPresent(accessToken -> jwtService.extractEmail(accessToken) + .ifPresentOrElse(email -> memberRepository.findByEmail(email).ifPresent(this::saveAuthentication), + () -> { + throw new InvalidTokenException("Invalid access token"); + } + ) + ); + } catch (Exception e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } filterChain.doFilter(request, response); } - public void saveAuthentication(Member member) { - String password = member.getPassword(); - if (password == null) { - password = PasswordUtil.generateRandomPassword(); - } + public void saveAuthentication(Member myMember) { + String password = PasswordUtil.generateRandomPassword(); - UserDetails userDetails = User.builder() - .username(member.getEmail()) + UserDetails userDetailsUser = User.builder() + .username(myMember.getEmail()) .password(password) - .roles(member.getRole().name()) + .roles(myMember.getRole().name()) .build(); - Authentication authentication = - new UsernamePasswordAuthenticationToken(userDetails, null, - authoritiesMapper.mapAuthorities(userDetails.getAuthorities())); + Authentication authentication = new UsernamePasswordAuthenticationToken(userDetailsUser, null, + authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities())); + SecurityContextHolder.getContext().setAuthentication(authentication); - String name = SecurityContextHolder.getContext().getAuthentication().getName(); - log.info("jwt authentication name : {}", name); } + } diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/domain/RefreshToken.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/domain/RefreshToken.java similarity index 92% rename from backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/domain/RefreshToken.java rename to backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/domain/RefreshToken.java index bd242ba4..5a54aab2 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/domain/RefreshToken.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/domain/RefreshToken.java @@ -1,4 +1,4 @@ -package com.rollthedice.backend.global.jwt.refresh.domain; +package com.rollthedice.backend.global.security.jwt.refresh.domain; import lombok.AccessLevel; import lombok.Getter; diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/repository/RefreshTokenRepository.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/repository/RefreshTokenRepository.java similarity index 61% rename from backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/repository/RefreshTokenRepository.java rename to backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/repository/RefreshTokenRepository.java index 7055e936..2d52ed21 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/repository/RefreshTokenRepository.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/repository/RefreshTokenRepository.java @@ -1,6 +1,6 @@ -package com.rollthedice.backend.global.jwt.refresh.repository; +package com.rollthedice.backend.global.security.jwt.refresh.repository; -import com.rollthedice.backend.global.jwt.refresh.domain.RefreshToken; +import com.rollthedice.backend.global.security.jwt.refresh.domain.RefreshToken; import org.springframework.data.repository.CrudRepository; import java.util.Optional; diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/service/RefreshTokenService.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/service/RefreshTokenService.java similarity index 76% rename from backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/service/RefreshTokenService.java rename to backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/service/RefreshTokenService.java index b9b878fb..d779e955 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/refresh/service/RefreshTokenService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/refresh/service/RefreshTokenService.java @@ -1,8 +1,8 @@ -package com.rollthedice.backend.global.jwt.refresh.service; +package com.rollthedice.backend.global.security.jwt.refresh.service; -import com.rollthedice.backend.global.jwt.exception.NotFoundTokenException; -import com.rollthedice.backend.global.jwt.refresh.domain.RefreshToken; -import com.rollthedice.backend.global.jwt.refresh.repository.RefreshTokenRepository; +import com.rollthedice.backend.global.security.jwt.exception.NotFoundTokenException; +import com.rollthedice.backend.global.security.jwt.refresh.domain.RefreshToken; +import com.rollthedice.backend.global.security.jwt.refresh.repository.RefreshTokenRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/service/JwtService.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/service/JwtService.java similarity index 64% rename from backend/core/src/main/java/com/rollthedice/backend/global/jwt/service/JwtService.java rename to backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/service/JwtService.java index 0332e118..0d6206a8 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/service/JwtService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/service/JwtService.java @@ -1,9 +1,12 @@ -package com.rollthedice.backend.global.jwt.service; +package com.rollthedice.backend.global.security.jwt.service; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; import com.rollthedice.backend.domain.member.repository.MemberRepository; -import com.rollthedice.backend.global.jwt.refresh.service.RefreshTokenService; +import com.rollthedice.backend.global.security.jwt.refresh.service.RefreshTokenService; +import com.rollthedice.backend.global.security.jwt.exception.NotFoundEmailException; +import com.rollthedice.backend.global.security.jwt.exception.NotFoundTokenException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.Getter; @@ -11,7 +14,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.Optional; @@ -21,31 +23,35 @@ @Getter @Slf4j public class JwtService { + private static final String ACCESS_TOKEN_SUBJECT = "AccessToken"; + private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken"; + private static final String EMAIL_CLAIM = "email"; + private static final String BEARER = "Bearer "; - @Value("${jwt.secret-key}") - private String secretKey; + private final MemberRepository memberRepository; + private final RefreshTokenService refreshTokenService; + @Value("${jwt.secretKey}") + private String secretKey; @Value("${jwt.access.expiration}") private Long accessTokenExpirationPeriod; - @Value("${jwt.refresh.expiration}") private Long refreshTokenExpirationPeriod; - @Value("${jwt.access.header}") private String accessHeader; - @Value("${jwt.refresh.header}") private String refreshHeader; - private static final String ACCESS_TOKEN_SUBJECT = "AccessToken"; - private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken"; - private static final String EMAIL_CLAIM = "email"; - private static final String BEARER = "Bearer "; + public void sendAccessAndRefreshToken(HttpServletResponse response, String email) { + setTokenHeader(response, accessHeader, createAccessToken(email)); - private final MemberRepository memberRepository; - private final RefreshTokenService refreshTokenService; + String refreshToken = createRefreshToken(); + setTokenHeader(response, refreshHeader, refreshToken); + refreshTokenService.updateToken(email, refreshToken); + log.info("Access Token, Refresh Token 헤더 설정 완료"); + } - public String createAccessToken(String email) { + private String createAccessToken(String email) { Date now = new Date(); return JWT.create() .withSubject(ACCESS_TOKEN_SUBJECT) @@ -54,7 +60,7 @@ public String createAccessToken(String email) { .sign(Algorithm.HMAC512(secretKey)); } - public String createRefreshToken() { + private String createRefreshToken() { Date now = new Date(); return JWT.create() .withSubject(REFRESH_TOKEN_SUBJECT) @@ -62,45 +68,35 @@ public String createRefreshToken() { .sign(Algorithm.HMAC512(secretKey)); } - public void sendAccessToken(HttpServletResponse response, String accessToken) { - response.setStatus(HttpServletResponse.SC_OK); - response.setHeader(accessHeader, accessToken); - } - - public void sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken) { - response.setStatus(HttpServletResponse.SC_OK); - response.setHeader(accessHeader, accessToken); - response.setHeader(refreshHeader, refreshToken); + public Optional extractRefreshToken(HttpServletRequest request) { + return Optional.ofNullable(request.getHeader(refreshHeader)) + .filter(refreshToken -> refreshToken.startsWith(BEARER)) + .map(refreshToken -> refreshToken.replace(BEARER, "")); } public Optional extractAccessToken(HttpServletRequest request) { return Optional.ofNullable(request.getHeader(accessHeader)) - .filter(accessToken -> accessToken.startsWith(BEARER)) - .map(accessToken -> accessToken.replace(BEARER, "")); - } - - public Optional extractRefreshToken(HttpServletRequest request) { - return Optional.ofNullable(request.getHeader(refreshHeader)) .filter(refreshToken -> refreshToken.startsWith(BEARER)) .map(refreshToken -> refreshToken.replace(BEARER, "")); } - public Optional extractEmail(String accessToken) { + public Optional extractEmail(String accessToken) throws JWTVerificationException { try { - return Optional.ofNullable(JWT.require(Algorithm.HMAC512(secretKey)) - .build() - .verify(accessToken) - .getClaim(EMAIL_CLAIM) - .asString()); + return Optional.ofNullable( + JWT.require(Algorithm.HMAC512(secretKey)).build().verify(accessToken).getClaim(EMAIL_CLAIM).asString()); } catch (Exception e) { log.error("액세스 토큰이 유효하지 않습니다."); return Optional.empty(); } } - @Transactional - public void updateRefreshToken(String email, String refreshToken) { - refreshTokenService.updateToken(email, refreshToken); + public String getEmail(HttpServletRequest request) { + String accessToken = this.extractAccessToken(request).orElseThrow(NotFoundTokenException::new); + return this.extractEmail(accessToken).orElseThrow(NotFoundEmailException::new); + } + + private void setTokenHeader(HttpServletResponse response, String headerName, String token) { + response.setHeader(headerName, BEARER + token); } public boolean isTokenValid(String token) { @@ -112,10 +108,5 @@ public boolean isTokenValid(String token) { return false; } } - - public void setRefreshTokenHeader(HttpServletResponse response, String refreshToken) { - response.setHeader(refreshHeader, BEARER + refreshToken); - } } - diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/util/PasswordUtil.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/util/PasswordUtil.java similarity index 94% rename from backend/core/src/main/java/com/rollthedice/backend/global/jwt/util/PasswordUtil.java rename to backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/util/PasswordUtil.java index a547a787..00f98756 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/jwt/util/PasswordUtil.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/util/PasswordUtil.java @@ -1,4 +1,4 @@ -package com.rollthedice.backend.global.jwt.util; +package com.rollthedice.backend.global.security.jwt.util; import java.util.Random; From 55848fce66ea972c337dba2a7aa780db1fae8920 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:28:11 +0900 Subject: [PATCH 36/60] feat: InvalidTokenException --- .../jwt/exception/InvalidTokenException.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/InvalidTokenException.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/InvalidTokenException.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/InvalidTokenException.java new file mode 100644 index 00000000..7ccce55e --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/InvalidTokenException.java @@ -0,0 +1,14 @@ +package com.rollthedice.backend.global.security.jwt.exception; + +public class InvalidTokenException extends RuntimeException { + public InvalidTokenException() { + } + + public InvalidTokenException(String message) { + super(message); + } + + public InvalidTokenException(String message, Throwable cause) { + super(message, cause); + } +} From 159a63c5cb22e4dbb1b93b54af3e933141fd3bd0 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:28:37 +0900 Subject: [PATCH 37/60] =?UTF-8?q?feat:=20NotFoundEmailException=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/exception/NotFoundEmailException.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/NotFoundEmailException.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/NotFoundEmailException.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/NotFoundEmailException.java new file mode 100644 index 00000000..c51a4009 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/exception/NotFoundEmailException.java @@ -0,0 +1,15 @@ +package com.rollthedice.backend.global.security.jwt.exception; + +public class NotFoundEmailException extends RuntimeException{ + public NotFoundEmailException() { + } + + public NotFoundEmailException(String message) { + super(message); + } + + public NotFoundEmailException(String message, Throwable cause) { + super(message, cause); + } +} + From e63eddab7f2bdb763e5a827f2d0019e743ba6d22 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:29:04 +0900 Subject: [PATCH 38/60] =?UTF-8?q?feat:=20LoginMemberArgumentResolver=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resolver/LoginMemberArgumentResolver.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/resolver/LoginMemberArgumentResolver.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/resolver/LoginMemberArgumentResolver.java b/backend/core/src/main/java/com/rollthedice/backend/global/resolver/LoginMemberArgumentResolver.java new file mode 100644 index 00000000..8890b2de --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/resolver/LoginMemberArgumentResolver.java @@ -0,0 +1,33 @@ +package com.rollthedice.backend.global.resolver; + +import com.rollthedice.backend.global.annotation.LoginMemberEmail; +import com.rollthedice.backend.global.security.jwt.service.JwtService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@RequiredArgsConstructor +@Component +@Slf4j +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver{ + private final JwtService jwtService; + + @Override + + public boolean supportsParameter(MethodParameter methodParameter) { + return methodParameter.hasParameterAnnotation(LoginMemberEmail.class); + } + + @Override + public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, + NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { + HttpServletRequest request = (HttpServletRequest)nativeWebRequest.getNativeRequest(); + return jwtService.getEmail(request); + } +} From d545a6b41ec4f8d9903b5604edcf9faa0b93f874 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:29:31 +0900 Subject: [PATCH 39/60] =?UTF-8?q?feat:=20OAuth2ProviderService=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth2/service/OAuth2ProviderService.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/OAuth2ProviderService.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/OAuth2ProviderService.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/OAuth2ProviderService.java new file mode 100644 index 00000000..a77aae75 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/OAuth2ProviderService.java @@ -0,0 +1,45 @@ +package com.rollthedice.backend.global.oauth2.service; + +import com.rollthedice.backend.global.oauth2.dto.LoginRequest; +import com.rollthedice.backend.global.oauth2.userInfo.KakaoOAuth2UserInfo; +import com.rollthedice.backend.global.oauth2.userInfo.OAuth2UserInfo; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + + +import java.util.Map; + +import static com.rollthedice.backend.domain.member.entity.SocialType.KAKAO; + + +@Service +@RequiredArgsConstructor +public class OAuth2ProviderService { + + public OAuth2UserInfo getUserInfo(LoginRequest request) { + return switch (request.getSocialType()) { + case APPLE -> getKakaoUserInfo(request); //apple 로그인 구현 미완성 + case KAKAO -> getKakaoUserInfo(request); + }; + } + + private OAuth2UserInfo getKakaoUserInfo(LoginRequest request) { + Map attributes = WebClient.create(KAKAO.getProviderUrl()) + .get() + .headers(httpHeaders -> { + httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + httpHeaders.setBearerAuth(request.getToken()); + }) + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(Map.class) + .log() + .block(); + + return new KakaoOAuth2UserInfo(attributes); + } + +} From 25a6fff0372a0a6c92f2272f0dad9cb941184250 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:31:10 +0900 Subject: [PATCH 40/60] =?UTF-8?q?refactor:=20AuthService=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/query/AuthService.java | 32 ---------- .../global/oauth2/service/AuthService.java | 60 +++++++++++++++++++ 2 files changed, 60 insertions(+), 32 deletions(-) delete mode 100644 backend/core/src/main/java/com/rollthedice/backend/domain/member/query/AuthService.java create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/AuthService.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/query/AuthService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/query/AuthService.java deleted file mode 100644 index 9b7a8662..00000000 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/query/AuthService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.rollthedice.backend.domain.member.query; - -import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.domain.member.repository.MemberRepository; -import com.rollthedice.backend.global.jwt.service.JwtService; -import com.rollthedice.backend.global.query.QueryService; -import jakarta.persistence.EntityNotFoundException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; - -@Slf4j -@QueryService -@RequiredArgsConstructor -public class AuthService { - private final MemberRepository memberRepository; - private final JwtService jwtService; - - public Long getMemberId() { - return getMember().getId(); - } - - public Member getMember() { - - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - UserDetails userDetails = (UserDetails) authentication.getPrincipal(); - return memberRepository.findByEmail(userDetails.getUsername()).orElseThrow(EntityNotFoundException::new); - } - -} diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/AuthService.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/AuthService.java new file mode 100644 index 00000000..3dd4beb4 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/AuthService.java @@ -0,0 +1,60 @@ +package com.rollthedice.backend.global.oauth2.service; + +import com.rollthedice.backend.domain.member.entity.Member; +import com.rollthedice.backend.domain.member.entity.Role; +import com.rollthedice.backend.domain.member.entity.SocialType; +import com.rollthedice.backend.domain.member.repository.MemberRepository; +import com.rollthedice.backend.global.oauth2.dto.LoginRequest; +import com.rollthedice.backend.global.oauth2.userInfo.OAuth2UserInfo; +import com.rollthedice.backend.global.security.jwt.service.JwtService; +import com.rollthedice.backend.global.query.QueryService; +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.UUID; + +@Slf4j +@QueryService +@RequiredArgsConstructor +public class AuthService { + private final MemberRepository memberRepository; + private final OAuth2ProviderService oAuth2ProviderService; + private final JwtService jwtService; + + public void authenticateOrRegisterUser(LoginRequest loginRequest, HttpServletResponse response) { + OAuth2UserInfo userInfo = oAuth2ProviderService.getUserInfo(loginRequest); + Member member = findOrElseRegisterMember(userInfo, loginRequest.getSocialType()); + jwtService.sendAccessAndRefreshToken(response, member.getEmail()); + } + + private Member findOrElseRegisterMember(OAuth2UserInfo userInfo, SocialType socialType) { + return memberRepository.findBySocialTypeAndOauthId(socialType, userInfo.getId()) + .orElse(registerMember(socialType, userInfo)); + } + + private Member registerMember(SocialType socialType, OAuth2UserInfo userInfo) { + Member member = Member.builder() + .socialType(socialType) + .oauthId(userInfo.getId()) + .email(UUID.randomUUID() + "@socialUser.com") + .nickname(String.valueOf(UUID.randomUUID())) + .imageUrl(userInfo.getImageUrl()) + .role(Role.USER) + .build(); + + return memberRepository.save(member); + } + + public Member getMember() { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + return memberRepository.findByEmail(userDetails.getUsername()).orElseThrow(EntityNotFoundException::new); + } + +} From f7bdd63b1d3909a5f12cec4c02225bc8c9722d6e Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:31:58 +0900 Subject: [PATCH 41/60] remove: OAuth2 Handler & CustomOAuth2UserService --- .../handler/OAuth2LoginFailureHandler.java | 22 ------ .../handler/OAuth2LoginSuccessHandler.java | 52 ------------- .../service/CustomOAuth2UserService.java | 73 ------------------- 3 files changed, 147 deletions(-) delete mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginFailureHandler.java delete mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginSuccessHandler.java delete mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/CustomOAuth2UserService.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginFailureHandler.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginFailureHandler.java deleted file mode 100644 index 1c759eda..00000000 --- a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginFailureHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.rollthedice.backend.global.oauth2.handler; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -@Slf4j -@Component -public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - response.getWriter().write("소셜 로그인을 실패하였습니다."); - log.info("소셜 로그인에 실패했습니다. 에러 메시지 : {}", exception.getMessage()); - } -} \ No newline at end of file diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginSuccessHandler.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginSuccessHandler.java deleted file mode 100644 index 4dc8696f..00000000 --- a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/handler/OAuth2LoginSuccessHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.rollthedice.backend.global.oauth2.handler; - -import com.rollthedice.backend.domain.member.entity.Role; -import com.rollthedice.backend.global.jwt.service.JwtService; -import com.rollthedice.backend.global.oauth2.CustomOAuth2User; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -@Slf4j -@Component -@RequiredArgsConstructor -public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { - private final JwtService jwtService; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - log.info("OAuth2 Login succeed."); - CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal(); - - if(oAuth2User.getRole() == Role.GUEST) { - String accessToken = jwtService.createAccessToken(oAuth2User.getEmail()); - response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken); - response.sendRedirect("oauth2/sign-up"); - - jwtService.sendAccessAndRefreshToken(response, accessToken, null); - } else { - loginSuccess(response, oAuth2User); - response.sendRedirect("/"); - } - } - - private void loginSuccess(HttpServletResponse response, CustomOAuth2User oAuth2User) throws IOException { - log.info("Role == User => refresh token 생성합니다."); - - String accessToken = jwtService.createAccessToken(oAuth2User.getEmail()); - String refreshToken = jwtService.createRefreshToken(); - response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken); - response.addHeader(jwtService.getRefreshHeader(), "Bearer " + refreshToken); - - jwtService.sendAccessAndRefreshToken(response, accessToken, refreshToken); - jwtService.updateRefreshToken(oAuth2User.getEmail(), refreshToken); - } -} - diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/CustomOAuth2UserService.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/CustomOAuth2UserService.java deleted file mode 100644 index f26741b1..00000000 --- a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/service/CustomOAuth2UserService.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.rollthedice.backend.global.oauth2.service; - - -import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.domain.member.entity.SocialType; -import com.rollthedice.backend.domain.member.repository.MemberRepository; -import com.rollthedice.backend.global.oauth2.CustomOAuth2User; -import com.rollthedice.backend.global.oauth2.OAuthAttributes; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; - -import java.util.Collections; -import java.util.Map; - -@Slf4j -@Service -@RequiredArgsConstructor -public class CustomOAuth2UserService implements OAuth2UserService { - private final MemberRepository memberRepository; - - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - log.info("CustomOAuth2UserService.loadUser() 실행 - OAuth2 로그인 요청 진입"); - - OAuth2UserService delegate = new DefaultOAuth2UserService(); - OAuth2User oAuth2User = delegate.loadUser(userRequest); - - String registrationId = userRequest.getClientRegistration().getRegistrationId(); - SocialType socialType = getSocialType(registrationId); - String userNameAttributeName = userRequest.getClientRegistration() - .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); - Map attributes = oAuth2User.getAttributes(); - - OAuthAttributes extractAttributes = - OAuthAttributes.of(socialType, userNameAttributeName, attributes); - - Member createdMember = getMember(extractAttributes, socialType); - - // DefaultOAuth2User를 구현한 CustomOAuth2User 객체를 생성해서 반환 - return new CustomOAuth2User( - Collections.singleton(new SimpleGrantedAuthority(createdMember.getRole().getString())), - attributes, - extractAttributes.getNameAttributeKey(), - createdMember.getEmail(), - createdMember.getRole() - ); - } - - private SocialType getSocialType(String registrationId) { - return SocialType.KAKAO; - } - - private Member getMember(OAuthAttributes attributes, SocialType socialType) { - Member findMember = memberRepository.findBySocialTypeAndOauthId(socialType, - attributes.getOauth2UserInfo().getId()).orElse(null); - if(findMember == null) { - return saveMember(attributes, socialType); - } - return findMember; - } - - private Member saveMember(OAuthAttributes attributes, SocialType socialType) { - Member createdUser = attributes.toEntity(socialType, attributes.getOauth2UserInfo()); - return memberRepository.save(createdUser); - } -} From fe369d3d42807fdf809ece5cb065a64564898c83 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:32:53 +0900 Subject: [PATCH 42/60] =?UTF-8?q?feat:=20LoginMemberEmail=20interface=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/global/annotation/LoginMemberEmail.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/annotation/LoginMemberEmail.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/annotation/LoginMemberEmail.java b/backend/core/src/main/java/com/rollthedice/backend/global/annotation/LoginMemberEmail.java new file mode 100644 index 00000000..859af590 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/annotation/LoginMemberEmail.java @@ -0,0 +1,11 @@ +package com.rollthedice.backend.global.annotation; + +import java.lang.annotation.*; + + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface LoginMemberEmail { +} + From 93578498b9a511f4901f69d760b8cbdc4631750f Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:37:16 +0900 Subject: [PATCH 43/60] =?UTF-8?q?refactor:=20AuthService=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=84=B0=EB=A6=AC=20=EB=B3=80=EA=B2=BD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rollthedice/backend/domain/news/service/NewsService.java | 2 +- .../com/rollthedice/backend/global/advice/ExceptionAdvice.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java index cb59ca9a..3ab16298 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java @@ -2,7 +2,7 @@ import com.rollthedice.backend.domain.bookmark.service.BookmarkService; import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.domain.member.query.AuthService; +import com.rollthedice.backend.global.oauth2.service.AuthService; import com.rollthedice.backend.domain.news.contentqueue.ContentProducer; import com.rollthedice.backend.domain.news.dto.ContentMessageDto; import com.rollthedice.backend.domain.news.dto.NewsUrlDto; diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/advice/ExceptionAdvice.java b/backend/core/src/main/java/com/rollthedice/backend/global/advice/ExceptionAdvice.java index 9ae7bf53..bd265554 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/advice/ExceptionAdvice.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/advice/ExceptionAdvice.java @@ -1,6 +1,6 @@ package com.rollthedice.backend.global.advice; -import com.rollthedice.backend.global.jwt.exception.NotFoundTokenException; +import com.rollthedice.backend.global.security.jwt.exception.NotFoundTokenException; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; From cdcfca9349096eb5b1fc61c6e731c9cc2d36ad14 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:37:42 +0900 Subject: [PATCH 44/60] =?UTF-8?q?chore:=20webflux=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/core/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/core/build.gradle b/backend/core/build.gradle index 71dc2d2f..c71f2554 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -37,6 +37,7 @@ dependencies { implementation 'org.jsoup:jsoup:1.15.3' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE' + implementation 'org.springframework.boot:spring-boot-starter-webflux' annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' From 846f135b78b4e0e42824b2c5b89f9c4404545d29 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 10:38:33 +0900 Subject: [PATCH 45/60] =?UTF-8?q?chore:=20AuthService=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=84=B0=EB=A6=AC=20=EB=B3=80=EA=B2=BD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/bookmark/service/BookmarkService.java | 3 +-- .../backend/domain/debate/service/DebateRoomService.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/service/BookmarkService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/service/BookmarkService.java index df91954e..83d85e7e 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/service/BookmarkService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/service/BookmarkService.java @@ -3,12 +3,11 @@ import com.rollthedice.backend.domain.bookmark.entity.Bookmark; import com.rollthedice.backend.domain.bookmark.repository.BookmarkRepository; import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.domain.member.query.AuthService; +import com.rollthedice.backend.global.oauth2.service.AuthService; import com.rollthedice.backend.domain.news.dto.response.NewsResponse; import com.rollthedice.backend.domain.news.entity.News; import com.rollthedice.backend.domain.news.mapper.NewsMapper; import com.rollthedice.backend.domain.news.repository.NewsRepository; -import com.rollthedice.backend.domain.news.service.NewsService; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/DebateRoomService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/DebateRoomService.java index 5a18f9a6..ba084130 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/DebateRoomService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/debate/service/DebateRoomService.java @@ -5,7 +5,7 @@ import com.rollthedice.backend.domain.debate.mapper.DebateRoomMapper; import com.rollthedice.backend.domain.debate.repository.DebateRoomRepository; import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.domain.member.query.AuthService; +import com.rollthedice.backend.global.oauth2.service.AuthService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; From 53eeecab1641b9d8ac1fb0522a3940bee482e776 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:02:02 +0900 Subject: [PATCH 46/60] =?UTF-8?q?feat:=20updateMember=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/MemberController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/MemberController.java index be1f8857..4817d16d 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/MemberController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/MemberController.java @@ -1,13 +1,14 @@ package com.rollthedice.backend.domain.member.controller; +import com.rollthedice.backend.domain.member.dto.MemberServiceDto; +import com.rollthedice.backend.domain.member.dto.MemberUpdateDto; import com.rollthedice.backend.domain.member.dto.response.MemberResponse; import com.rollthedice.backend.domain.member.service.MemberService; +import com.rollthedice.backend.global.annotation.LoginMemberEmail; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -15,6 +16,19 @@ public class MemberController { private final MemberService memberService; + @PostMapping + public ResponseEntity updateMember(@LoginMemberEmail String email, + @RequestBody MemberUpdateDto memberUpdateDto) { + MemberServiceDto memberServiceDto = memberUpdateDto.toServiceDto(email); + + if (memberService.isDuplicatedNickname(memberServiceDto)) { + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } + memberService.update(memberServiceDto); + + return ResponseEntity.status(HttpStatus.OK).build(); + } + @ResponseStatus(HttpStatus.OK) @GetMapping("") public MemberResponse getMemberInfo() { From 7e4d2f10975092786fdd8ae1442646a4d1deac3d Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:02:42 +0900 Subject: [PATCH 47/60] =?UTF-8?q?feat:=20existsMemberByNickname=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/member/repository/MemberRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/repository/MemberRepository.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/repository/MemberRepository.java index 0f504f18..0e28fa7e 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/repository/MemberRepository.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/repository/MemberRepository.java @@ -12,4 +12,6 @@ public interface MemberRepository extends JpaRepository { Optional findByEmail(String email); Optional findBySocialTypeAndOauthId(SocialType socialType, String oauthId); + + boolean existsMemberByNickname(String nickname); } From c815aec1ecf8041e60631da60442f65e24250e9d Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:02:56 +0900 Subject: [PATCH 48/60] =?UTF-8?q?feat:=20JpaAuditingConfig=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/global/config/JpaAuditingConfig.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/config/JpaAuditingConfig.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/config/JpaAuditingConfig.java b/backend/core/src/main/java/com/rollthedice/backend/global/config/JpaAuditingConfig.java new file mode 100644 index 00000000..73972576 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/config/JpaAuditingConfig.java @@ -0,0 +1,9 @@ +package com.rollthedice.backend.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaAuditingConfig { +} From b7c6bd0d586416d035a41c364b300ffac0093d0b Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:03:07 +0900 Subject: [PATCH 49/60] =?UTF-8?q?feat:=20AuthController=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth2/controller/AuthController.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/oauth2/controller/AuthController.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/controller/AuthController.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/controller/AuthController.java new file mode 100644 index 00000000..18a20501 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/controller/AuthController.java @@ -0,0 +1,23 @@ +package com.rollthedice.backend.global.oauth2.controller; + +import com.rollthedice.backend.global.oauth2.dto.LoginRequest; +import com.rollthedice.backend.global.oauth2.service.AuthService; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class AuthController { + private final AuthService authService; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest request, HttpServletResponse response) { + authService.authenticateOrRegisterUser(request, response); + return new ResponseEntity<>(HttpStatus.OK); + } +} \ No newline at end of file From fc3cedc25d769afd44004938cbf05093fb340376 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:22:47 +0900 Subject: [PATCH 50/60] fix: secretKey -> secret-key --- .../backend/global/security/jwt/service/JwtService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/service/JwtService.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/service/JwtService.java index 0d6206a8..73c89ee0 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/service/JwtService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/service/JwtService.java @@ -31,7 +31,7 @@ public class JwtService { private final MemberRepository memberRepository; private final RefreshTokenService refreshTokenService; - @Value("${jwt.secretKey}") + @Value("${jwt.secret-key}") private String secretKey; @Value("${jwt.access.expiration}") private Long accessTokenExpirationPeriod; From a4211efe1a728a4edd95f1ede3fad2e918966efb Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:25:22 +0900 Subject: [PATCH 51/60] =?UTF-8?q?refactor:=20refreshtoken=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/filter/JwtAuthenticationProcessingFilter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/filter/JwtAuthenticationProcessingFilter.java b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/filter/JwtAuthenticationProcessingFilter.java index 9a414821..f7d3baf6 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/filter/JwtAuthenticationProcessingFilter.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/security/jwt/filter/JwtAuthenticationProcessingFilter.java @@ -33,7 +33,7 @@ public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter { private final MemberRepository memberRepository; private final RefreshTokenService refreshTokenService; - private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, @@ -42,8 +42,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse filterChain.doFilter(request, response); return; } + String refreshToken = jwtService.extractRefreshToken(request) - .filter(jwtService::isTokenValid) .orElse(null); if (refreshToken != null) { @@ -52,7 +52,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse return; } - log.info("refresh token is null"); checkAccessTokenAndAuthentication(request, response, filterChain); } @@ -76,6 +75,7 @@ public void checkAccessTokenAndAuthentication(HttpServletRequest request, HttpSe ) ); } catch (Exception e) { + response.sendError(HttpServletResponse.SC_FORBIDDEN); } filterChain.doFilter(request, response); @@ -95,6 +95,6 @@ public void saveAuthentication(Member myMember) { SecurityContextHolder.getContext().setAuthentication(authentication); } - } + From 55dead66d61c827df2955f684b230ea16c4ed4d4 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:26:30 +0900 Subject: [PATCH 52/60] =?UTF-8?q?feat:=20LoginRequest=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/global/oauth2/dto/LoginRequest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 backend/core/src/main/java/com/rollthedice/backend/global/oauth2/dto/LoginRequest.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/dto/LoginRequest.java b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/dto/LoginRequest.java new file mode 100644 index 00000000..0dbf8e09 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/global/oauth2/dto/LoginRequest.java @@ -0,0 +1,17 @@ +package com.rollthedice.backend.global.oauth2.dto; + +import com.rollthedice.backend.domain.member.entity.SocialType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Schema(description = "로그인 포맷") +public class LoginRequest { + @Schema(description = "인증서버에서 받아온 access token을 입력") + private String token; + @Schema(description = "인증서버타입, APPLE, KAKAO 가능") + private SocialType socialType; +} From a4704b00da8e001a26d584e72f12f078a579e51e Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:40:58 +0900 Subject: [PATCH 53/60] =?UTF-8?q?feat:=20SocialType=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/entity/SocialType.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/SocialType.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/SocialType.java index 5095a4ff..abd61227 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/SocialType.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/SocialType.java @@ -1,6 +1,20 @@ package com.rollthedice.backend.domain.member.entity; +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter public enum SocialType { - APPLE, - KAKAO + APPLE("https://"), + KAKAO("https://kapi.kakao.com/v2/user/me"); + + + private final String providerUrl; + + @JsonCreator + public static SocialType from(String s) { + return SocialType.valueOf(s.toUpperCase()); + } } From b31af0fadcbbcfc63a59c1c737072262ed5c6315 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:42:46 +0900 Subject: [PATCH 54/60] =?UTF-8?q?refactor:=20sdk=EB=A5=BC=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/global/config/SecurityConfig.java | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java b/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java index 8ab6b4ac..84e4d84f 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java @@ -1,19 +1,13 @@ -package com.rollthedice.backend.global.config; - import com.rollthedice.backend.domain.member.repository.MemberRepository; -import com.rollthedice.backend.global.jwt.filter.JwtAuthenticationProcessingFilter; -import com.rollthedice.backend.global.jwt.refresh.service.RefreshTokenService; -import com.rollthedice.backend.global.jwt.service.JwtService; -import com.rollthedice.backend.global.oauth2.handler.OAuth2LoginFailureHandler; -import com.rollthedice.backend.global.oauth2.handler.OAuth2LoginSuccessHandler; -import com.rollthedice.backend.global.oauth2.service.CustomOAuth2UserService; +import com.rollthedice.backend.global.security.jwt.filter.JwtAuthenticationProcessingFilter; +import com.rollthedice.backend.global.security.jwt.refresh.service.RefreshTokenService; +import com.rollthedice.backend.global.security.jwt.service.JwtService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; @@ -24,34 +18,26 @@ @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { + private final JwtService jwtService; private final MemberRepository memberRepository; - private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; - private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; - private final CustomOAuth2UserService customOAuth2UserService; private final RefreshTokenService refreshTokenService; - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http - .formLogin(AbstractHttpConfigurer::disable) //FormLogin 사용 X - .httpBasic(AbstractHttpConfigurer::disable) //httpBasic 사용 X + .formLogin(AbstractHttpConfigurer::disable) // 기본 제공하는 로그인 Form 사용 X + .httpBasic(AbstractHttpConfigurer::disable) // Bearer방식이기 때문에 httpBasic 사용 X .csrf(AbstractHttpConfigurer::disable) //csrf 보안 사용 X - .headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable).disable()) - .sessionManagement((sessionManagement) -> + .sessionManagement((sessionManagement) -> // 세션은 사용하지 않기 때문에 stateless 설정 sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(authorizeRequests -> authorizeRequests - .anyRequest().permitAll()) - .oauth2Login(oauth2 -> oauth2 - .loginPage("/login") - .successHandler(oAuth2LoginSuccessHandler) - .failureHandler(oAuth2LoginFailureHandler) - .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) //customUserService 설정 - ); + .anyRequest().permitAll()); + http.addFilterAfter(jwtAuthenticationProcessingFilter(), LogoutFilter.class); + return http.build(); } @@ -62,6 +48,6 @@ public PasswordEncoder passwordEncoder() { @Bean public JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter() { - return new JwtAuthenticationProcessingFilter(jwtService, refreshTokenService, memberRepository); + return new JwtAuthenticationProcessingFilter(jwtService, memberRepository, refreshTokenService); } } From 1beb7f5c04208c291db4da2a5157fdc531372422 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:43:27 +0900 Subject: [PATCH 55/60] =?UTF-8?q?feat:=20member=20update=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/member/entity/Member.java | 3 +- .../domain/member/service/MemberService.java | 28 +++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java index 4efff30d..6395057d 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/entity/Member.java @@ -30,11 +30,10 @@ public class Member extends BaseTimeEntity { @Enumerated(EnumType.STRING) private Status status; - public Member update(MemberServiceDto dto) { + public void update(MemberServiceDto dto) { this.email = dto.getEmail(); this.imageUrl = dto.getImageUrl(); this.nickname = dto.getNickname(); - return this; } public void signUp(String nickname) { diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/service/MemberService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/service/MemberService.java index 3150a4f8..9e88af51 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/member/service/MemberService.java @@ -1,11 +1,14 @@ package com.rollthedice.backend.domain.member.service; +import com.rollthedice.backend.domain.member.dto.MemberServiceDto; import com.rollthedice.backend.domain.member.dto.SignUpDto; import com.rollthedice.backend.domain.member.dto.response.MemberResponse; import com.rollthedice.backend.domain.member.entity.Member; -import com.rollthedice.backend.domain.member.query.AuthService; -import com.rollthedice.backend.global.jwt.refresh.service.RefreshTokenService; -import com.rollthedice.backend.global.jwt.service.JwtService; +import com.rollthedice.backend.domain.member.repository.MemberRepository; +import com.rollthedice.backend.global.oauth2.service.AuthService; +import com.rollthedice.backend.global.security.jwt.refresh.service.RefreshTokenService; +import com.rollthedice.backend.global.security.jwt.service.JwtService; +import jakarta.persistence.EntityNotFoundException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -20,15 +23,22 @@ public class MemberService { private final JwtService jwtService; private final HttpServletRequest request; private final HttpServletResponse response; + private final MemberRepository memberRepository; + + + @Transactional(readOnly = true) + public boolean isDuplicatedNickname(MemberServiceDto memberServiceDto) { + return memberRepository.existsMemberByNickname(memberServiceDto.getNickname()); + } @Transactional - public void signUp(SignUpDto dto) { - Member member = authService.getMember(); - member.signUp(dto.getNickname()); + public void update(MemberServiceDto memberServiceDto) { + findByEmail(memberServiceDto.getEmail()).update(memberServiceDto); + } - String refreshToken = jwtService.createRefreshToken(); - jwtService.setRefreshTokenHeader(response, refreshToken); - refreshTokenService.updateToken(member.getEmail(), refreshToken); + @Transactional(readOnly = true) + public Member findByEmail(String email) { + return memberRepository.findByEmail(email).orElseThrow(EntityNotFoundException::new); } public MemberResponse getMemberInfo() { From d1855b8f30328573726045c9066d59fe343450e9 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:44:49 +0900 Subject: [PATCH 56/60] remove: LoginController --- .../member/controller/LoginController.java | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/LoginController.java diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/LoginController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/LoginController.java deleted file mode 100644 index 219f3c68..00000000 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/member/controller/LoginController.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.rollthedice.backend.domain.member.controller; - -import com.rollthedice.backend.domain.member.dto.SignUpDto; -import com.rollthedice.backend.domain.member.service.MemberService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -public class LoginController { - private final MemberService memberService; - - @ResponseStatus(HttpStatus.OK) - @PostMapping("/oauth2/sign-up") - public void signUp(@RequestBody SignUpDto dto) { - memberService.signUp(dto); - } - -} From a5b27b410a19065c81d0fca23d53bf193d5e1b37 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:45:19 +0900 Subject: [PATCH 57/60] =?UTF-8?q?fix:=20@EnableJpaAuditing=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/rollthedice/backend/BackendApplication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/core/src/main/java/com/rollthedice/backend/BackendApplication.java b/backend/core/src/main/java/com/rollthedice/backend/BackendApplication.java index 13a91fbb..4364e06a 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/BackendApplication.java +++ b/backend/core/src/main/java/com/rollthedice/backend/BackendApplication.java @@ -5,7 +5,6 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.scheduling.annotation.EnableScheduling; -@EnableJpaAuditing @EnableScheduling @SpringBootApplication public class BackendApplication { From 8656db79d545c2a9307b2ad8bf1807fca24b4721 Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:45:49 +0900 Subject: [PATCH 58/60] =?UTF-8?q?fix:=20apple=20silicon=20chip=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/core/build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/core/build.gradle b/backend/core/build.gradle index c71f2554..4d2ceb6e 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -22,6 +22,9 @@ repositories { } dependencies { + if (isAppleSilicon()) { + runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.94.Final:osx-aarch_64") + } implementation 'com.auth0:java-jwt:4.2.1' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' @@ -53,3 +56,7 @@ tasks.named('bootBuildImage') { tasks.named('test') { useJUnitPlatform() } + +def boolean isAppleSilicon() { + return System.getProperty("os.name") == "Mac OS X" && System.getProperty("os.arch") == "aarch64" +} \ No newline at end of file From 341fc049ed3903beb27f24d6810b005d26662f3a Mon Sep 17 00:00:00 2001 From: yeonjy Date: Tue, 30 Apr 2024 15:50:51 +0900 Subject: [PATCH 59/60] test: Login Token --- .../backend/domain/auth/AuthTest.java | 109 ++++++++++++++++++ .../backend/domain/member/MemberFixture.java | 48 ++++++++ .../rollthedice/backend/global/LoginTest.java | 65 +++++++++++ 3 files changed, 222 insertions(+) create mode 100644 backend/core/src/test/java/com/rollthedice/backend/domain/auth/AuthTest.java create mode 100644 backend/core/src/test/java/com/rollthedice/backend/domain/member/MemberFixture.java create mode 100644 backend/core/src/test/java/com/rollthedice/backend/global/LoginTest.java diff --git a/backend/core/src/test/java/com/rollthedice/backend/domain/auth/AuthTest.java b/backend/core/src/test/java/com/rollthedice/backend/domain/auth/AuthTest.java new file mode 100644 index 00000000..1df44ec6 --- /dev/null +++ b/backend/core/src/test/java/com/rollthedice/backend/domain/auth/AuthTest.java @@ -0,0 +1,109 @@ +package com.rollthedice.backend.domain.auth; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.rollthedice.backend.domain.member.controller.MemberController; +import com.rollthedice.backend.domain.member.dto.MemberUpdateDto; +import com.rollthedice.backend.domain.member.service.MemberService; +import com.rollthedice.backend.global.LoginTest; +import com.rollthedice.backend.global.security.jwt.refresh.domain.RefreshToken; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.Date; + +import static com.rollthedice.backend.domain.member.MemberFixture.MEMBER; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DisplayName("JWT 인증테스트의 ") +@WebMvcTest(MemberController.class) +public class AuthTest extends LoginTest { + + @MockBean + private MemberService memberService; + + @Test + @DisplayName("Access Token을 이용한 정상 로그인") + public void accessToken_로그인_성공() throws Exception { + // when + final ResultActions perform = mockMvc.perform(post("/members").contentType(MediaType.APPLICATION_JSON) + .content(toRequestBody(new MemberUpdateDto("yeonjy", "imageUrl2"))) + .header("Authorization", "Bearer " + accessToken)); + + // then + perform.andExpect(status().isOk()); + } + + @Test + @DisplayName("Access Token 기간 만료로 인한 멤버 업데이트 실패") + public void access_token_기간만료() throws Exception { + // given -> 시간이 만료된 accessToken 생성 + Date now = new Date(); + String accessToken = JWT.create() + .withSubject("AccessToken") + .withExpiresAt(new Date(now.getTime() - 1000)) + .withClaim("email", MEMBER().getEmail()) + .sign(Algorithm.HMAC512(secretKey)); + + // when + final ResultActions perform = mockMvc.perform(post("/members").contentType(MediaType.APPLICATION_JSON) + .content(toRequestBody(new MemberUpdateDto("yeonjy", "imageUrl2"))) + .header("Authorization", "Bearer " + accessToken)); + + // then + perform.andExpect(status().isForbidden()); + } + + @Test + @DisplayName("Refresh Token 전송으로 인한 access_token refresh_token 재발급") + public void refreshToken과_accessToken_재발급() throws Exception { + // given -> refresh Token 세팅 및 redis 에서 refresh Token이 있는지 조회 + Date now = new Date(); + String refreshToken = JWT.create() + .withSubject("RefreshToken") + .withExpiresAt(new Date(now.getTime() + 18000)) + .sign(Algorithm.HMAC512(secretKey)); + + RefreshToken token = new RefreshToken(MEMBER().getEmail()); + + given(refreshTokenService.findByToken(refreshToken)).willReturn(token); + + // when + final ResultActions perform = mockMvc.perform(post("/members").contentType(MediaType.APPLICATION_JSON) + .content(toRequestBody(new MemberUpdateDto("yeonjy", "imageUrl2"))) + .header("Authorization-refresh", "Bearer " + refreshToken)).andDo(print()); + + // then + perform.andExpect(status().isForbidden()) + .andExpect(header().exists("Authorization")) + .andExpect(header().exists("Authorization-refresh")); + } + + @Test + @DisplayName("Refresh Token 만료") + public void refreshToken_만료() throws Exception { + // given -> 만료된 refreshToken 설정 + Date now = new Date(); + String refreshToken = JWT.create() + .withSubject("RefreshToken") + .withExpiresAt(new Date(now.getTime() - 1000)) + .sign(Algorithm.HMAC512(secretKey)); + + // when + final ResultActions perform = mockMvc.perform(post("/members").contentType(MediaType.APPLICATION_JSON) + .content(toRequestBody(new MemberUpdateDto("yeonjy", "imageUrl2"))) + .header("Authorization-refresh", "Bearer " + refreshToken)).andDo(print()); + + // then + perform.andExpect(status().isForbidden()); + } +} + diff --git a/backend/core/src/test/java/com/rollthedice/backend/domain/member/MemberFixture.java b/backend/core/src/test/java/com/rollthedice/backend/domain/member/MemberFixture.java new file mode 100644 index 00000000..ce19038e --- /dev/null +++ b/backend/core/src/test/java/com/rollthedice/backend/domain/member/MemberFixture.java @@ -0,0 +1,48 @@ +package com.rollthedice.backend.domain.member; + +import com.rollthedice.backend.domain.member.dto.MemberServiceDto; +import com.rollthedice.backend.domain.member.entity.Member; +import com.rollthedice.backend.domain.member.entity.Role; +import com.rollthedice.backend.domain.member.entity.SocialType; + +public class MemberFixture { + public final static Member MEMBER() { + return Member.builder() + .email("yeonjy@ourservice.com") + .role(Role.USER) + .imageUrl("imageUrl") + .nickname("yeonjy") + .socialType(SocialType.KAKAO) + .oauthId("-2") + .build(); + + } + + public final static Member SECOND_MEMBER() { + return Member.builder() + .email("realhsb@ourservice.com") + .role(Role.USER) + .imageUrl("imageUrl") + .nickname("realhsb") + .socialType(SocialType.KAKAO) + .oauthId("-1") + .build(); + } + + public final static MemberServiceDto MEMBER_SERVICE_DTO() { + return MemberServiceDto.builder() + .imageUrl("imageUrl") + .nickname("yeonjy") + .email("yeonjy@ourservice.com") + .build(); + } + + public final static MemberServiceDto UPDATE_MEMBER_SERVICE_DTO() { + return MemberServiceDto.builder() + .imageUrl("imageUrl2") + .nickname("yeonjy") + .email("yeonjy@ourservice.com") + .build(); + } + +} diff --git a/backend/core/src/test/java/com/rollthedice/backend/global/LoginTest.java b/backend/core/src/test/java/com/rollthedice/backend/global/LoginTest.java new file mode 100644 index 00000000..dc64f64d --- /dev/null +++ b/backend/core/src/test/java/com/rollthedice/backend/global/LoginTest.java @@ -0,0 +1,65 @@ +package com.rollthedice.backend.global; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.rollthedice.backend.domain.member.entity.Member; +import com.rollthedice.backend.domain.member.repository.MemberRepository; +import com.rollthedice.backend.global.security.jwt.filter.JwtAuthenticationProcessingFilter; +import com.rollthedice.backend.global.security.jwt.refresh.service.RefreshTokenService; +import com.rollthedice.backend.global.security.jwt.service.JwtService; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.Date; + +import static com.rollthedice.backend.domain.member.MemberFixture.MEMBER; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +@WebMvcTest +public abstract class LoginTest { + protected MockMvc mockMvc; + @Value("${jwt.secret-key}") + protected String secretKey; + protected String accessToken; + @SpyBean + protected JwtService jwtService; + @MockBean + protected RefreshTokenService refreshTokenService; + @MockBean + protected MemberRepository memberRepository; + protected Member loginMember; + @Autowired + private ObjectMapper objectMapper; + + protected String toRequestBody(Object value) throws JsonProcessingException { + return objectMapper.writeValueAsString(value); + } + + @BeforeEach + public void loginSetup(WebApplicationContext ctx) { + mockMvc = MockMvcBuilders + .webAppContextSetup(ctx) + .addFilter(new JwtAuthenticationProcessingFilter(jwtService, memberRepository, refreshTokenService)) + .alwaysDo(print()) + .build(); + + loginMember = MEMBER(); + + Date now = new Date(); + accessToken = JWT.create() + .withSubject("AccessToken") + .withExpiresAt(new Date(now.getTime() + 18000)) + .withClaim("email", loginMember.getEmail()) + .sign(Algorithm.HMAC512(secretKey)); + } +} + From 34b3eaaf8e704c82ba516b8a07e71d728a8df88d Mon Sep 17 00:00:00 2001 From: yeonjy <81320703+yeonjy@users.noreply.github.com> Date: Tue, 30 Apr 2024 16:09:34 +0900 Subject: [PATCH 60/60] =?UTF-8?q?hotfix:=20package=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/rollthedice/backend/global/config/SecurityConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java b/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java index 84e4d84f..6b532daf 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java @@ -1,3 +1,5 @@ +package com.rollthedice.backend.global.config; + import com.rollthedice.backend.domain.member.repository.MemberRepository; import com.rollthedice.backend.global.security.jwt.filter.JwtAuthenticationProcessingFilter; import com.rollthedice.backend.global.security.jwt.refresh.service.RefreshTokenService;