diff --git a/backend/core/build.gradle b/backend/core/build.gradle index 4ec647e5..4f334773 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -45,12 +45,12 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' testAnnotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' - testCompileOnly 'org.projectlombok:lombok' - testAnnotationProcessor 'org.projectlombok:lombok' } tasks.named('bootBuildImage') { diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java index ca710d00..d9673019 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkApi.java @@ -1,5 +1,6 @@ package com.rollthedice.backend.domain.bookmark.api; +import com.rollthedice.backend.domain.bookmark.dto.response.BookmarkResponse; import com.rollthedice.backend.domain.news.dto.response.NewsResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -12,24 +13,40 @@ public interface BookmarkApi { @Operation( - summary = "북마크 조회", + summary = "북마크 전체 조회", description = "회원이 북마크한 뉴스를 페이지로 나누어 조회합니다.", security = {@SecurityRequirement(name = "access_token")}, - tags = {"bookmark"} + tags = {"북마크"} ) @ApiResponse( responseCode = "200", description = "OK" ) - List getBookmarked( + List getAllBookmarkedNews( Pageable pageable ); + @Operation( + summary = "뉴스 북마크 여부 조회", + description = "로그인한 회원이 해당 뉴스를 북마크 했는지 여부를 조회합니다.", + security = {@SecurityRequirement(name = "access_token")}, + tags = {"북마크"} + ) + @ApiResponse( + responseCode = "200", + description = "OK" + ) + BookmarkResponse getIsBookmarked( + @Parameter(in = ParameterIn.PATH, description = "뉴스 ID", required = true) + Long newsId + ); + + @Operation( summary = "북마크 저장", description = "뉴스에 대하여 북마크로 저장합니다.", security = {@SecurityRequirement(name = "access_token")}, - tags = {"bookmark"} + tags = {"북마크"} ) @ApiResponse( responseCode = "201", @@ -44,7 +61,7 @@ void saveBookmark( summary = "북마크 삭제", description = "저장된 북마크를 해제합니다.", security = {@SecurityRequirement(name = "access_token")}, - tags = {"bookmark"} + tags = {"북마크"} ) @ApiResponse( responseCode = "204", diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java index 6d4ad706..23dd1b85 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/api/BookmarkController.java @@ -1,5 +1,6 @@ package com.rollthedice.backend.domain.bookmark.api; +import com.rollthedice.backend.domain.bookmark.dto.response.BookmarkResponse; import com.rollthedice.backend.domain.bookmark.service.BookmarkService; import com.rollthedice.backend.domain.news.dto.response.NewsResponse; import lombok.RequiredArgsConstructor; @@ -18,8 +19,15 @@ public class BookmarkController implements BookmarkApi { @ResponseStatus(HttpStatus.OK) @GetMapping("") @Override - public List getBookmarked(final Pageable pageable) { - return bookmarkService.getBookmarkedNews(pageable); + public List getAllBookmarkedNews(final Pageable pageable) { + return bookmarkService.getAllBookmarkedNews(pageable); + } + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{newsId}") + @Override + public BookmarkResponse getIsBookmarked(@PathVariable final Long newsId) { + return bookmarkService.getIsBookmarked(newsId); } @ResponseStatus(HttpStatus.CREATED) diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/dto/response/BookmarkResponse.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/dto/response/BookmarkResponse.java new file mode 100644 index 00000000..cd5ed0d4 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/dto/response/BookmarkResponse.java @@ -0,0 +1,16 @@ +package com.rollthedice.backend.domain.bookmark.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BookmarkResponse { + private Long id; + private Boolean isBookmarked; + +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/repository/BookmarkRepository.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/repository/BookmarkRepository.java index a107402f..e8b9c2e1 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/repository/BookmarkRepository.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/repository/BookmarkRepository.java @@ -12,6 +12,7 @@ @Repository public interface BookmarkRepository extends JpaRepository { Boolean existsBookmarkByMemberAndNews(Member member, News news); + Boolean existsBookmarkByMemberAndNewsId(Member member, Long newsId); List findAllByMemberOrderByCreatedAt(Member member, Pageable pageable); 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 8716860f..ecdb5adb 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 @@ -1,5 +1,6 @@ package com.rollthedice.backend.domain.bookmark.service; +import com.rollthedice.backend.domain.bookmark.dto.response.BookmarkResponse; import com.rollthedice.backend.domain.bookmark.entity.Bookmark; import com.rollthedice.backend.domain.bookmark.repository.BookmarkRepository; import com.rollthedice.backend.domain.member.entity.Member; @@ -11,14 +12,15 @@ import com.rollthedice.backend.domain.news.repository.NewsRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; -@RequiredArgsConstructor @Service +@RequiredArgsConstructor public class BookmarkService { private final AuthService authService; private final BookmarkRepository bookmarkRepository; @@ -31,7 +33,7 @@ public boolean isBookmarked(Member member, News news) { } @Transactional(readOnly = true) - public List getBookmarkedNews(Pageable pageable) { + public List getAllBookmarkedNews(Pageable pageable) { Member member = authService.getMember(); return bookmarkRepository.findAllByMemberOrderByCreatedAt(member, pageable).stream() .map(bookmark -> newsMapper.toResponse(bookmark.getNews(), true)) @@ -53,5 +55,14 @@ public void saveBookmark(Long newsId) { public void deleteBookmark(Long newsId) { bookmarkRepository.deleteByNewsId(newsId); } + + public BookmarkResponse getIsBookmarked(Long newsId) { + Member member = authService.getMember(); + Boolean isBookmarked = bookmarkRepository.existsBookmarkByMemberAndNewsId(member, newsId); + return BookmarkResponse.builder() + .id(newsId) + .isBookmarked(isBookmarked) + .build(); + } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/config/SwaggerConfig.java b/backend/core/src/main/java/com/rollthedice/backend/global/config/SwaggerConfig.java index df2e83ef..8efe8fca 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/config/SwaggerConfig.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/config/SwaggerConfig.java @@ -28,7 +28,6 @@ public OpenAPI openAPI() { private List getServers() { return List.of(new Server() - .url("/api") .description("백엔드 api 서버") ); } diff --git a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj index f1ca2bac..6c29b5d5 100644 --- a/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj +++ b/iOS/RollTheDice/RollTheDice.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ 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 */; }; + 6C41B8DA2BE104A800274FA4 /* RecentNewsCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C41B8D92BE104A800274FA4 /* RecentNewsCardView.swift */; }; + 6C41B8DC2BE1095800274FA4 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C41B8DB2BE1095800274FA4 /* ChatListView.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 */; }; @@ -97,6 +99,8 @@ 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 = ""; }; + 6C41B8D92BE104A800274FA4 /* RecentNewsCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentNewsCardView.swift; sourceTree = ""; }; + 6C41B8DB2BE1095800274FA4 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.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 = ""; }; @@ -218,6 +222,15 @@ path = Report; sourceTree = ""; }; + 6C41B8D62BE1048500274FA4 /* ChatList */ = { + isa = PBXGroup; + children = ( + 6C41B8D92BE104A800274FA4 /* RecentNewsCardView.swift */, + 6C41B8DB2BE1095800274FA4 /* ChatListView.swift */, + ); + path = ChatList; + sourceTree = ""; + }; 6C454A762B9DA62C006FD9D0 /* SignUp */ = { isa = PBXGroup; children = ( @@ -324,6 +337,7 @@ 6C7704902B7229B6001B17CB /* Debate */ = { isa = PBXGroup; children = ( + 6C41B8D62BE1048500274FA4 /* ChatList */, 6CE103182BD57A1600498AA4 /* Summary */, 6CDB29F72BAA06FB0081037B /* ChatGPT */, 6C3237B32B7C433000B699AB /* ChatType */, @@ -652,6 +666,7 @@ 6CDB29FF2BAA08280081037B /* GPTChatListViewModel.swift in Sources */, 6CF130AD2BAB0C4400A437B6 /* AuthenticationViewModel.swift in Sources */, 6CDB29F92BAA07350081037B /* GPTChat.swift in Sources */, + 6C41B8DC2BE1095800274FA4 /* ChatListView.swift in Sources */, 6CDB29FD2BAA07FD0081037B /* GPTChatView.swift in Sources */, 6C41B8D22BDE696200274FA4 /* NewsType.swift in Sources */, 357666102BBD4BF6002C226A /* ReportListView.swift in Sources */, @@ -689,6 +704,7 @@ 6C77048C2B722686001B17CB /* MainTabView.swift in Sources */, 6C7704992B722A20001B17CB /* MainTabViewModel.swift in Sources */, 6C454A7C2B9DA71C006FD9D0 /* SignUpView.swift in Sources */, + 6C41B8DA2BE104A800274FA4 /* RecentNewsCardView.swift in Sources */, 6CF130C72BAB7B9800A437B6 /* RollTheDiceAPINews.swift in Sources */, 6CE1031A2BD57A2500498AA4 /* DebateSummaryView.swift in Sources */, 6CE1030C2BD56A4000498AA4 /* TypeReportView.swift in Sources */, diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ChatListView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ChatListView.swift new file mode 100644 index 00000000..31461291 --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/ChatListView.swift @@ -0,0 +1,91 @@ +// +// ChatListView.swift +// RollTheDice +// +// Created by Subeen on 4/30/24. +// + +import SwiftUI + +struct ChatListView: View { + + @EnvironmentObject var pathModel: PathModel + + var body: some View { + ZStack { + Color.backgroundDark.ignoresSafeArea(.all) + ScrollView { + VStack(alignment: .leading, spacing: 44) { + recentNewsView + debateChatListView + } + .padding(.horizontal, 120) + } + .scrollIndicators(.hidden) + } + } + + @ViewBuilder + var recentNewsView: some View { + VStack(alignment: .leading) { + Text("최근 본 뉴스") + .foregroundStyle(.basicWhite) + .font(.pretendardBold32) + HStack { + RecentNewsCardView() + Spacer() + RecentNewsCardView() + Spacer() + RecentNewsCardView() + } + } + } + + @ViewBuilder + var debateChatListView: some View { + VStack(alignment: .leading, spacing: 26) { + Text("채팅방") + .foregroundStyle(.basicWhite) + .font(.pretendardBold32) + debateChatCellView + debateChatCellView + debateChatCellView + } + } + + var debateChatCellView: some View { + HStack { + HStack(alignment: .center, spacing: 16) { + Text("🏛️") + .padding(.leading, 26) + .font(.pretendardBold32) + Text("경제 기사 경제 기사저제목목제목 제목") + .foregroundStyle(.gray07) + .font(.pretendardBold24) + .padding(.vertical, 24) + + Spacer() + Image(.chevronRight) + } + .background(.gray01) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .padding(.trailing, 16) + + //TODO: 버튼 영역 수정하기 + Button { + pathModel.paths.append(.debateSummaryView) + } label: { + Image(.chevronLeft) +// .background(.gray01) + + } + .frame(width: 80, height: 80) + .background(.gray01) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + } +} + +#Preview { + ChatListView() +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsCardView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsCardView.swift new file mode 100644 index 00000000..fa3d688c --- /dev/null +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatList/RecentNewsCardView.swift @@ -0,0 +1,59 @@ +// +// RecentNewsCardView.swift +// RollTheDice +// +// Created by Subeen on 4/30/24. +// + +import SwiftUI + +struct RecentNewsCardView: View { + var body: some View { + HStack { + titleView + } +// .frame(width: 260, height: 244) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + + var titleView: some View { + ZStack { + VStack(alignment: .center, spacing: 20) { + Image(.chevronLeft) + Text("제목ahrhrhra제목 제목 미젝ㅁㄱㅈ 메좀ㄱㅈ게조깆거ㅣㅓ린러.ㄴ릴") + .multilineTextAlignment(.center) + .foregroundStyle(.gray07) + .font(.pretendardBold24) + .frame(width: 240, height: 96) + .padding(.horizontal, 10) + .padding(.vertical, 30) + .background(.gray01) + .clipShape(RoundedRectangle(cornerRadius: 2)) + .overlay { + RoundedRectangle(cornerRadius: 2) + .stroke(Color.gray05, lineWidth: 1.0) + } + .shadow(color: .basicBlack.opacity(0.1), radius: 2) + Button { + + } label: { + Text("토론 시작하기") + .foregroundStyle(.basicWhite) + .font(.pretendardRegular14) + .padding(.horizontal, 38) + .padding(.vertical, 10) + .background(.primary01) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + } + } + .padding(.horizontal, 20) + .padding(.top, 24) + .padding(.bottom, 32) + .background(.gray02) + } +} + +#Preview { + RecentNewsCardView() +} diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift index f93b8c35..ece55640 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/ChatType/ChatTypeView.swift @@ -15,18 +15,7 @@ struct ChatTypeView: View { ZStack { Color.backgroundDark.ignoresSafeArea(.all) VStack { - CustomNavigationBar( - isDisplayTrailingBtn: true, - trailingItems: [ - (Image(.bookmarkfill), { pathModel.paths.append(.bookmarkView)}), - (Image(.profileWhite), { pathModel.paths.append(.mypageView)}) - ] - ) - Spacer() - recentReadNewsView - Spacer() - - chatListView + } diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift index 8c17b97c..62319d1e 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Debate/Summary/DebateSummaryView.swift @@ -58,8 +58,10 @@ struct DebateSummaryView: View { VStack(spacing: 20) { Rectangle() .frame(height: 1) + .foregroundStyle(.gray07) Text("“ 이번 토론의 핵심 내용을 알려줄게요! ”") + .foregroundStyle(.gray07) .font(.pretendardBold40) .padding(.top, 40) @@ -68,7 +70,7 @@ struct DebateSummaryView: View { .foregroundStyle(.gray03) .overlay { Text("요약") - + .foregroundStyle(.gray07) } ZStack(alignment: .topLeading) { Rectangle() @@ -99,6 +101,7 @@ struct DebateSummaryView: View { Spacer() Rectangle() .frame(height: 1) + .foregroundStyle(.gray07) } .padding(.horizontal, 40) .padding(.vertical, 40) diff --git a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift index bd9ca7d9..e5b895ef 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/MainTab/MainTabView.swift @@ -42,7 +42,7 @@ struct MainTabView: View { // .environmentObject(newsListViewModel) .tag(1) - ChatTypeView() + ChatListView() // .environmentObject(pathModel) .tabItem { Image(systemName: "message") diff --git a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift index f1b36734..8f026af7 100644 --- a/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift +++ b/iOS/RollTheDice/RollTheDice/Source/View/Report/Daily/DailyBarChartView.swift @@ -108,8 +108,10 @@ struct DailyBarChartView: View { VStack(alignment: .center) { Text("\(selectedValue?.date.formatted(date: .numeric, time: .omitted) ?? "")") .font(.pretendardRegular14) + .foregroundStyle(.gray02) Text("\(selectedValue?.views ?? 0)") .font(.pretendardBold24) + .foregroundStyle(.basicWhite) } .padding(10)