From 6708f644165803f25199f29e0077ba984ebeeaf0 Mon Sep 17 00:00:00 2001 From: EnesBaserr Date: Fri, 6 Dec 2024 19:38:32 +0300 Subject: [PATCH 01/36] Fix code exec. response --- .../Controllers/CodeExecutionController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/CodeExecutionController.java b/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/CodeExecutionController.java index 9281be82..f3207a81 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/CodeExecutionController.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/CodeExecutionController.java @@ -27,6 +27,10 @@ public ResponseEntity> executeCode( ) { try { CodeExecutionResponseDTO responseDTO = codeExecutionService.executeCode(requestDto.getCode(), requestDto.getLanguage(), requestDto.getInput()); + int status = responseDTO.getStatus().equals("Accepted") ? HttpStatus.OK.value() : HttpStatus.INTERNAL_SERVER_ERROR.value(); + if (status == HttpStatus.INTERNAL_SERVER_ERROR.value()) { + throw new Exception(responseDTO.getStatus()); + } GenericApiResponse response = ApiResponseBuilder.buildSuccessResponse( responseDTO.getClass(), From d5a3cec979da33974c94fef0244af977d68c1516 Mon Sep 17 00:00:00 2001 From: EnesBaserr Date: Fri, 6 Dec 2024 20:11:07 +0300 Subject: [PATCH 02/36] Fix question related endpoints to retrieve difficulty level. --- .../DTOs/Responses/GetQuestionDetailsResponseDto.java | 2 ++ .../programminglanguagesforum/Services/QuestionService.java | 1 + 2 files changed, 3 insertions(+) diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/GetQuestionDetailsResponseDto.java b/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/GetQuestionDetailsResponseDto.java index e72ae89b..4e18f6b4 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/GetQuestionDetailsResponseDto.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/GetQuestionDetailsResponseDto.java @@ -1,5 +1,6 @@ package com.group1.programminglanguagesforum.DTOs.Responses; +import com.group1.programminglanguagesforum.Entities.DifficultyLevel; import lombok.*; import java.util.ArrayList; @@ -18,6 +19,7 @@ public class GetQuestionDetailsResponseDto { private Long dislikeCount; private Long commentCount; private Boolean selfQuestion; + private DifficultyLevel difficulty; private Integer selfVoted; private String createdAt; @Builder.Default diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Services/QuestionService.java b/backend/src/main/java/com/group1/programminglanguagesforum/Services/QuestionService.java index cf9404cf..c45141e5 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Services/QuestionService.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Services/QuestionService.java @@ -110,6 +110,7 @@ public GetQuestionDetailsResponseDto getQuestion(Long id) throws NoSuchElementEx .id(question.getId()) .title(question.getTitle()) .content(question.getQuestionBody()) + .difficulty(question.getDifficulty()) .likeCount(question.getUpvoteCount()) .dislikeCount(question.getDownvoteCount()) .commentCount(question.getCommentCount()) From c8b9817ff901f68020073dc586ad3d5d8d5e4339 Mon Sep 17 00:00:00 2001 From: EnesBaserr Date: Fri, 6 Dec 2024 21:06:56 +0300 Subject: [PATCH 03/36] Add followed tags to users' profile responses. --- .../Config/ModelMapperConfig.java | 7 +++++++ .../Controllers/UserController.java | 13 +++++++++---- .../DTOs/Responses/SelfProfileResponseDto.java | 15 +++++++++++++++ .../DTOs/Responses/UserProfileResponseDto.java | 6 ++++++ .../Repositories/TagRepository.java | 4 ++++ .../Services/TagService.java | 10 ++++++++++ 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Config/ModelMapperConfig.java b/backend/src/main/java/com/group1/programminglanguagesforum/Config/ModelMapperConfig.java index dc617d07..5fe26a6c 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Config/ModelMapperConfig.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Config/ModelMapperConfig.java @@ -46,6 +46,13 @@ protected void configure() { } }); + modelMapper.addMappings(new PropertyMap () { + @Override + protected void configure() { + skip(destination.getFollowedTags()); + + } + }); modelMapper.addMappings(new PropertyMap() { @Override protected void configure() { diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/UserController.java b/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/UserController.java index d15adf83..8ecfcaa3 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/UserController.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Controllers/UserController.java @@ -6,10 +6,7 @@ import com.group1.programminglanguagesforum.Entities.User; import com.group1.programminglanguagesforum.Exceptions.UnauthorizedAccessException; import com.group1.programminglanguagesforum.Exceptions.UserNotFoundException; -import com.group1.programminglanguagesforum.Services.AnswerService; -import com.group1.programminglanguagesforum.Services.QuestionService; -import com.group1.programminglanguagesforum.Services.UserContextService; -import com.group1.programminglanguagesforum.Services.UserService; +import com.group1.programminglanguagesforum.Services.*; import com.group1.programminglanguagesforum.Util.ApiResponseBuilder; import lombok.RequiredArgsConstructor; import org.modelmapper.ModelMapper; @@ -33,6 +30,7 @@ public class UserController extends BaseController { private final ModelMapper modelMapper; private final QuestionService questionService; private final AnswerService answerService; + private final TagService tagService; @GetMapping(value = EndpointConstants.UserEndpoints.USER_ME) public ResponseEntity> getUser() { @@ -43,6 +41,9 @@ public ResponseEntity> getUser() { SelfProfileResponseDto.class); List questions = questionService.findByAuthorId(user.getId()); List answers = answerService.findByAnsweredBy(user.getId()); + selfProfileResponseDto.setFollowedTags( + tagService.getFollowedTags(user.getId()) + ); selfProfileResponseDto.setQuestionCount((long) questions.size()); selfProfileResponseDto.setQuestions( questions); @@ -81,6 +82,10 @@ public ResponseEntity> getUserById( UserProfileResponseDto userProfileResponseDto = modelMapper.map(user.get(), UserProfileResponseDto.class); userProfileResponseDto.setSelfFollowing(userService.selfFollowing(user.get())); + userProfileResponseDto.setFollowedTags( + tagService.getFollowedTags(user.get().getId()) + ); + GenericApiResponse response = ApiResponseBuilder.buildSuccessResponse( userProfileResponseDto.getClass(), diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/SelfProfileResponseDto.java b/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/SelfProfileResponseDto.java index 2685cc1f..f243d681 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/SelfProfileResponseDto.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/SelfProfileResponseDto.java @@ -1,5 +1,6 @@ package com.group1.programminglanguagesforum.DTOs.Responses; import com.group1.programminglanguagesforum.Entities.ExperienceLevel; +import com.group1.programminglanguagesforum.Entities.TagType; import lombok.*; import java.util.List; @@ -25,4 +26,18 @@ public class SelfProfileResponseDto { private ExperienceLevel experienceLevel; private List questions; private List answers; + private List followedTags; + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Getter + @Setter + public static class FollowedTags { + + private Long id; + private String name; + private TagType tagType; + private String description; + + } } diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/UserProfileResponseDto.java b/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/UserProfileResponseDto.java index 3fa6629d..942d33a0 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/UserProfileResponseDto.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/DTOs/Responses/UserProfileResponseDto.java @@ -2,6 +2,9 @@ import com.group1.programminglanguagesforum.Entities.ExperienceLevel; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Builder @NoArgsConstructor @AllArgsConstructor @@ -20,4 +23,7 @@ public class UserProfileResponseDto { private boolean selfFollowing; private int reputationPoints; private ExperienceLevel experienceLevel; + @Builder.Default + private List followedTags = new ArrayList<>(); + } diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Repositories/TagRepository.java b/backend/src/main/java/com/group1/programminglanguagesforum/Repositories/TagRepository.java index b62ec825..da282981 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Repositories/TagRepository.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Repositories/TagRepository.java @@ -5,6 +5,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -14,4 +16,6 @@ public interface TagRepository extends JpaRepository { List findAllByIdIn(List ids); Page findTagsByTagNameContainingIgnoreCase(String tagName, Pageable pageable); + @Query("SELECT t FROM Tag t JOIN t.followers u WHERE u.id = :userId") + List findTagByFollowers(@Param("userId") Long userId); } diff --git a/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java b/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java index 2eeeb004..4483efc0 100644 --- a/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java +++ b/backend/src/main/java/com/group1/programminglanguagesforum/Services/TagService.java @@ -101,6 +101,16 @@ public GetTagDetailsResponseDto getTagDetails(Long tagId) { .build(); } + public List getFollowedTags(Long userId) { + return tagRepository.findTagByFollowers(userId).stream() + .map(tag -> SelfProfileResponseDto.FollowedTags.builder() + .id(tag.getId()) + .name(tag.getTagName()) + .tagType(getTagType(tag)) + .description(tag.getTagDescription()) + .build()) + .toList(); + } public Page searchTags(String q, Pageable pageable) { Page tags = tagRepository.findTagsByTagNameContainingIgnoreCase(q, pageable); From baee8372019726c723599fd1545ea324aea59bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atakan=20Ya=C5=9Far?= Date: Sat, 7 Dec 2024 00:21:51 +0300 Subject: [PATCH 04/36] feat(mobile): add menu to navigate bookmarks --- mobile/app/(tabs)/profile.tsx | 43 ++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/mobile/app/(tabs)/profile.tsx b/mobile/app/(tabs)/profile.tsx index 80ba1cef..c738cdaf 100644 --- a/mobile/app/(tabs)/profile.tsx +++ b/mobile/app/(tabs)/profile.tsx @@ -6,6 +6,7 @@ import { Button, ButtonText, HStack, + Icon, Image, Input, InputField, @@ -25,14 +26,16 @@ import { TextareaInput, VStack, } from "@/components/ui"; +import { Menu, MenuItem, MenuItemLabel, MenuSeparator } from "@/components/ui/menu"; + import { useGetUserProfile, useUpdateUserProfile, } from "@/services/api/programmingForumComponents"; import { ExperienceLevel } from "@/services/api/programmingForumSchemas"; import useAuthStore from "@/services/auth"; -import { Link } from "expo-router"; -import { ChevronDownIcon, Plus } from "lucide-react-native"; +import { Link, router } from "expo-router"; +import { ChevronDownIcon, Plus, Bookmark, MenuIcon, LogOutIcon } from "lucide-react-native"; import { useEffect, useState } from "react"; export default function Profile() { @@ -103,10 +106,38 @@ export function UserProfile({ userId }: { userId: string }) { return ( - - - {me ? "My profile" : "Profile"} - + + + + {me ? "My Profile" : "Profile"} + + + { + return ( + + + ) + }} + > + router.push(`/users/bookmark`)}> + + Bookmarks + + + + + router.push(`/logout`)}> + + Logout + + + + + + Date: Sat, 7 Dec 2024 01:41:19 +0300 Subject: [PATCH 05/36] feat(mobile): implement bookmarks page and API integration --- mobile/app/(tabs)/profile.tsx | 2 +- mobile/app/bookmarks.tsx | 69 ++++++++++++++++++ .../api/programmingForumComponents.ts | 70 +++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 mobile/app/bookmarks.tsx diff --git a/mobile/app/(tabs)/profile.tsx b/mobile/app/(tabs)/profile.tsx index c738cdaf..45d4c05a 100644 --- a/mobile/app/(tabs)/profile.tsx +++ b/mobile/app/(tabs)/profile.tsx @@ -122,7 +122,7 @@ export function UserProfile({ userId }: { userId: string }) { ) }} > - router.push(`/users/bookmark`)}> + router.push(`/bookmarks`)}> Bookmarks diff --git a/mobile/app/bookmarks.tsx b/mobile/app/bookmarks.tsx new file mode 100644 index 00000000..8fa5d016 --- /dev/null +++ b/mobile/app/bookmarks.tsx @@ -0,0 +1,69 @@ +import ErrorAlert from "@/components/ErrorAlert"; +import { FullscreenLoading } from "@/components/FullscreenLoading"; +import { QuestionCard } from "@/components/QuestionCard"; +import { + HStack, + ScrollView, + Text, + View, + VStack, +} from "@/components/ui"; +import { useGetBookmarkedQuestions } from "@/services/api/programmingForumComponents"; +import { QuestionSummary } from "@/services/api/programmingForumSchemas"; +import useAuthStore from "@/services/auth"; + +export default function BookmarkedQuestionsPage() { + const auth = useAuthStore(); + + const { data, isLoading, error } = useGetBookmarkedQuestions({}); + + const questions = data?.data || []; + + if (isLoading) { + return ; + } + + if (error) { + return ; + } + + if (!questions.length) { + return ( + + No bookmarked questions found. + + ); + } + + return ( + + + + Bookmarked Questions + + + + + + { + questions + .sort((a: QuestionSummary, b: QuestionSummary) => a.createdAt < b.createdAt ? 1 : -1) + .map((question: QuestionSummary) => ( + + ))} + + + + + ); +} diff --git a/mobile/services/api/programmingForumComponents.ts b/mobile/services/api/programmingForumComponents.ts index 9607cb9a..87c3eb98 100644 --- a/mobile/services/api/programmingForumComponents.ts +++ b/mobile/services/api/programmingForumComponents.ts @@ -1047,6 +1047,71 @@ export const useDownvoteQuestion = ( }); }; + +export type GetBookmarkedQuestionsError = Fetcher.ErrorWrapper< + | { + status: 401; + payload: Responses.UnauthorizedResponse; + } + | { + status: 404; + payload: Responses.NotFoundResponse; + } +>; + +export type GetBookmarkedQuestionsResponse = { + /** + * Internal status code of the response. An HTTP 200 response with an internal 500 status code is an error response. Prioritize the inner status over the HTTP status. + * + * @example 200 + * @example 201 + */ + status: 200 | 201; + data: Record | Schemas.QuestionSummary[]; +} + +export type GetBookmarkedQuestionsVariables = ProgrammingForumContext["fetcherOptions"]; + +export const fetchGetBookmarkedQuestions = ( + variables: GetBookmarkedQuestionsVariables, + signal?: AbortSignal, +) => + programmingForumFetch< + GetBookmarkedQuestionsResponse, + GetBookmarkedQuestionsError, + undefined, + {}, + {}, + {} + >({ url: "/questions/bookmarked", method: "get", ...variables, signal }); + +export const useGetBookmarkedQuestions = ( + variables: GetBookmarkedQuestionsVariables, + options?: Omit< + reactQuery.UseQueryOptions< + GetBookmarkedQuestionsResponse, + GetBookmarkedQuestionsError, + TData + >, + "queryKey" | "queryFn" | "initialData" + >, +) => { + const { fetcherOptions, queryOptions, queryKeyFn } = + useProgrammingForumContext(options); + return reactQuery.useQuery< + GetBookmarkedQuestionsResponse, + GetBookmarkedQuestionsError, + TData + >({ + queryKey: queryKeyFn({ path: "/questions/bookmarked", operationId: "getBookmarkedQuestions", variables }), + queryFn: ({ signal }) => + fetchGetBookmarkedQuestions({ ...fetcherOptions, ...variables }, signal), + ...options, + ...queryOptions, + }); +} + + export type BookmarkQuestionPathParams = { questionId: number; }; @@ -2263,4 +2328,9 @@ export type QueryOperation = path: "/feed"; operationId: "getUserFeed"; variables: GetUserFeedVariables; + } + | { + path: "/questions/bookmarked"; + operationId: "getBookmarkedQuestions"; + variables: GetBookmarkedQuestionsVariables; }; From 50990a821621c6a6a8294dca63c55d31245b05d9 Mon Sep 17 00:00:00 2001 From: Nazire Date: Sat, 7 Dec 2024 12:50:14 +0300 Subject: [PATCH 06/36] successfully added executable code snippets in question page. --- dev.yml | 2 ++ frontend/src/routes/question.tsx | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dev.yml b/dev.yml index 08ced51e..93cbd3fd 100644 --- a/dev.yml +++ b/dev.yml @@ -24,6 +24,8 @@ services: platform: linux/amd64 volumes: - ./backend:/app + ports: + - "8080:8080" environment: DB_HOST: db DB_NAME: cuisines-test diff --git a/frontend/src/routes/question.tsx b/frontend/src/routes/question.tsx index 9bb190a5..bfe83703 100644 --- a/frontend/src/routes/question.tsx +++ b/frontend/src/routes/question.tsx @@ -19,6 +19,7 @@ import { convertTagToTrack, useExercismSearch } from "@/services/exercism"; import { Flag, MessageSquare, ThumbsDown, ThumbsUp, Trash } from "lucide-react"; import { useState } from "react"; import { Link, useParams } from "react-router-dom"; +import { ContentWithSnippets } from "@/components/ContentWithSnippets"; export default function QuestionPage() { const { questionId } = useParams(); @@ -218,9 +219,7 @@ export default function QuestionPage() { {/* Question Content */} -
- {question.content} -
+ {/* Answers Section */}

Answers

From f560373ec33efccc4f4ad555ad6dc64745e4fe6e Mon Sep 17 00:00:00 2001 From: Nazire Date: Sat, 7 Dec 2024 14:26:51 +0300 Subject: [PATCH 07/36] successfully added preview option to description in question creation page. --- frontend/src/routes/create-question.tsx | 79 ++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/frontend/src/routes/create-question.tsx b/frontend/src/routes/create-question.tsx index 40126ef6..0e9502c9 100644 --- a/frontend/src/routes/create-question.tsx +++ b/frontend/src/routes/create-question.tsx @@ -13,6 +13,7 @@ import { useCreateQuestion, useSearchTags, } from "@/services/api/programmingForumComponents"; +import { Info } from "lucide-react"; import { queryKeyFn } from "@/services/api/programmingForumContext"; import { TagDetails } from "@/services/api/programmingForumSchemas"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -21,6 +22,9 @@ import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useNavigate, useSearchParams } from "react-router-dom"; import { z } from "zod"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { ContentWithSnippets } from "@/components/ContentWithSnippets"; // Schema validation for the form const newQuestionSchema = z.object({ @@ -57,6 +61,9 @@ export default function QuestionCreationPage() { console.log("tagIds", tagIds); console.log("form.getValues(tags)", form.getValues("tags")); + const [content, setContent] = useState(""); + const [isPreviewMode, setIsPreviewMode] = useState(false); + const { handleSubmit, control } = form; const queryClient = useQueryClient(); @@ -112,7 +119,54 @@ export default function QuestionCreationPage() { return (
+

Create a new question

+ + + + + +
+

Writing Questions

+

+ We use Markdown for formatting questions. You can use standard + Markdown syntax for headers, lists, links, etc. For a basic + reference, you can check{" "} + + CommonMark + + . +

+ +

Code Execution

+

+ To create executable code blocks, use triple backticks with + language-exec: +

+ + +

Linking

+

+ Link to tags using: [tag name](#tag-123) +
+ Link to questions using: [question title](#q-456) +

+
+
+
+
+ +
{ @@ -141,16 +195,39 @@ export default function QuestionCreationPage() { /> {/* Content */} +
+ + +
+ ( -