diff --git a/src/components/activity/input/ActivityItemLikes.tsx b/src/components/activity/input/ActivityItemLikes.tsx index 7b92db7..5bde08a 100644 --- a/src/components/activity/input/ActivityItemLikes.tsx +++ b/src/components/activity/input/ActivityItemLikes.tsx @@ -1,41 +1,23 @@ import React from "react"; -import useUserId from "@/components/auth/hooks/useUserId"; -import { useUserLike } from "@/components/statistics/hooks/useUserLike"; -import { StatisticsActionDto } from "@/wrapper/server"; -import sourceType = StatisticsActionDto.sourceType; -import { ActionIcon, Group, Text } from "@mantine/core"; -import { redirectToAuth } from "supertokens-auth-react"; -import { IconThumbUp } from "@tabler/icons-react"; -import useOnMobile from "@/components/general/hooks/useOnMobile"; +import { + Activity, + FindOneStatisticsDto, + StatisticsActionDto, +} from "@/wrapper/server"; +import ItemLikesButton from "@/components/statistics/input/ItemLikesButton"; +import sourceType = FindOneStatisticsDto.sourceType; interface Props { - activityId: string; + activity: Activity; } -const ActivityItemLikes = ({ activityId }: Props) => { - const userId = useUserId(); - const [likesCount, isLiked, toggleLike] = useUserLike({ - sourceId: activityId, - targetUserId: userId, - sourceType: sourceType.ACTIVITY, - }); +const ActivityItemLikes = ({ activity }: Props) => { return ( - { - if (!userId) { - await redirectToAuth(); - return; - } - toggleLike(); - }} - variant={isLiked ? "filled" : "transparent"} - size={"lg"} - color={isLiked ? "brand" : "white"} - data-disabled={!userId} - > - - {likesCount} - + ); }; diff --git a/src/components/activity/item/CollectionEntryActivityItem.tsx b/src/components/activity/item/CollectionEntryActivityItem.tsx index 0011f2b..ddee8a9 100644 --- a/src/components/activity/item/CollectionEntryActivityItem.tsx +++ b/src/components/activity/item/CollectionEntryActivityItem.tsx @@ -111,7 +111,7 @@ const CollectionEntryActivityItem = ({ activity }: Props) => { - + diff --git a/src/components/activity/item/ReviewActivityItem.tsx b/src/components/activity/item/ReviewActivityItem.tsx index db522d3..3409335 100644 --- a/src/components/activity/item/ReviewActivityItem.tsx +++ b/src/components/activity/item/ReviewActivityItem.tsx @@ -98,7 +98,7 @@ const ReviewActivityItem = ({ activity }: Props) => { size={"md"} /> - + diff --git a/src/components/collection/collection-entry/hooks/useOwnCollectionEntryForGameId.ts b/src/components/collection/collection-entry/hooks/useOwnCollectionEntryForGameId.ts index ed63e17..b30ed87 100644 --- a/src/components/collection/collection-entry/hooks/useOwnCollectionEntryForGameId.ts +++ b/src/components/collection/collection-entry/hooks/useOwnCollectionEntryForGameId.ts @@ -29,6 +29,7 @@ export function useOwnCollectionEntryForGameId( return null; } }, + enabled: gameId != undefined, }), queryKey, invalidate, diff --git a/src/components/collection/form/modal/CollectionRemoveModal.tsx b/src/components/collection/form/modal/CollectionRemoveModal.tsx index eb60e44..b209c90 100644 --- a/src/components/collection/form/modal/CollectionRemoveModal.tsx +++ b/src/components/collection/form/modal/CollectionRemoveModal.tsx @@ -65,7 +65,7 @@ const CollectionRemoveModal = ({ collectionId, opened, onClose }: Props) => { }} color={"red"} > - I'm sure + Confirm diff --git a/src/components/comment/editor/CommentEditor.tsx b/src/components/comment/editor/CommentEditor.tsx new file mode 100644 index 0000000..25b35d7 --- /dev/null +++ b/src/components/comment/editor/CommentEditor.tsx @@ -0,0 +1,34 @@ +import React, { useState } from "react"; +import { BubbleMenu, EditorOptions, useEditor } from "@tiptap/react"; +import { StarterKit } from "@tiptap/starter-kit"; +import { RichTextEditor } from "@mantine/tiptap"; + +interface Props extends Partial {} + +export const COMMENT_EDITOR_EXTENSIONS = [StarterKit]; + +const CommentEditor = ({ ...editorOptions }: Props) => { + const editor = useEditor( + { + ...editorOptions, + extensions: COMMENT_EDITOR_EXTENSIONS, + }, + [editorOptions.content], + ); + + return ( + + {editor && ( + + + + + + + )} + + + ); +}; + +export default CommentEditor; diff --git a/src/components/comment/editor/CommentEditorView.tsx b/src/components/comment/editor/CommentEditorView.tsx new file mode 100644 index 0000000..b22349b --- /dev/null +++ b/src/components/comment/editor/CommentEditorView.tsx @@ -0,0 +1,154 @@ +import React, { + MutableRefObject, + RefObject, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + ActionIcon, + Box, + Button, + Group, + LoadingOverlay, + Stack, + Text, +} from "@mantine/core"; +import CommentEditor from "@/components/comment/editor/CommentEditor"; +import { IconX } from "@tabler/icons-react"; +import { Editor } from "@tiptap/core"; +import { + useMutation, + UseMutationOptions, + useQueryClient, +} from "@tanstack/react-query"; +import { CommentService } from "@/wrapper/server"; +import { CreateCommentDto } from "@/wrapper/server"; +import { notifications } from "@mantine/notifications"; +import { useComment } from "@/components/comment/hooks/useComment"; + +interface Props { + /** + * If available, user will be able to modify this comment.
+ * Ideally, should be cleared when 'onCancel' is called. + */ + commentId?: string; + onCancel: () => void; + sourceType: CreateCommentDto.sourceType; + sourceId: string; + editorContainerRef?: RefObject; +} + +const CommentEditorView = ({ + commentId, + sourceType, + sourceId, + onCancel, + editorContainerRef, +}: Props) => { + const queryClient = useQueryClient(); + const editorRef = useRef(); + const commentQuery = useComment(commentId, sourceType); + const [previousContent, setPreviousContent] = useState( + undefined, + ); + const [shouldShowActionButtons, setShouldShowActionButtons] = + useState(false); + + const clearEditor = () => { + editorRef.current?.commands.clearContent(); + setShouldShowActionButtons(false); + onCancel(); + }; + + const commentMutation = useMutation({ + mutationFn: async () => { + if (editorRef.current == undefined) return; + + const content = editorRef.current?.getHTML(); + if (commentId) { + return CommentService.commentControllerUpdate(commentId, { + sourceType, + content: content, + }); + } + + return CommentService.commentControllerCreate({ + sourceId, + sourceType, + content: content, + }); + }, + onSettled: () => { + queryClient.invalidateQueries({ + queryKey: ["comments", sourceType, sourceId], + }); + }, + onSuccess: () => { + notifications.show({ + color: "green", + message: "Successfully submitted your comment!", + }); + clearEditor(); + }, + }); + + const isUpdateAction = + commentId != undefined && commentQuery.data != undefined; + + useEffect(() => { + if (commentId == undefined && previousContent != undefined) { + setPreviousContent(undefined); + } + + if (commentId != undefined && commentQuery.data != undefined) { + setPreviousContent(commentQuery.data.content); + setShouldShowActionButtons(true); + } + }, [commentId, commentQuery.data, previousContent]); + + return ( + + + {isUpdateAction && ( + + You are currently editing one of your previous comments. + + )} + { + setShouldShowActionButtons(true); + }} + onCreate={(props) => { + editorRef.current = props.editor; + }} + /> + {shouldShowActionButtons && ( + + { + clearEditor(); + }} + > + + + + + )} + + ); +}; + +export default CommentEditorView; diff --git a/src/components/comment/hooks/useComment.ts b/src/components/comment/hooks/useComment.ts new file mode 100644 index 0000000..42a20a7 --- /dev/null +++ b/src/components/comment/hooks/useComment.ts @@ -0,0 +1,29 @@ +import { CommentService, FindAllCommentsDto } from "@/wrapper/server"; +import sourceType = FindAllCommentsDto.sourceType; +import { useQuery } from "@tanstack/react-query"; + +/** + * Retrieves a single comment based on criteria. + * Will only be enabled if commentId is not null. + */ +export function useComment( + commentId: string | undefined, + sourceType: sourceType, +) { + return useQuery({ + queryKey: ["comment", "findOne", sourceType, commentId], + queryFn: async () => { + if (!commentId) { + return null; + } + + return CommentService.commentControllerFindOneById( + sourceType.valueOf(), + commentId, + ); + }, + enabled: commentId != undefined, + retry: 1, + staleTime: Infinity, + }); +} diff --git a/src/components/comment/hooks/useComments.ts b/src/components/comment/hooks/useComments.ts new file mode 100644 index 0000000..1e96d43 --- /dev/null +++ b/src/components/comment/hooks/useComments.ts @@ -0,0 +1,53 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { ExtendedUseQueryResult } from "@/util/types/ExtendedUseQueryResult"; +import { + CommentService, + FindAllCommentsDto, + FindCommentsPaginatedResponseDto, +} from "@/wrapper/server"; + +const DEFAULT_USE_COMMENTS_ORDER_BY = { + createdAt: "DESC", +}; + +export interface UseCommentsProps extends FindAllCommentsDto { + enabled?: boolean; + offset?: number; + limit?: number; +} + +export function useComments({ + enabled = true, + sourceId, + sourceType, + offset = 0, + limit = 10, + orderBy = DEFAULT_USE_COMMENTS_ORDER_BY, +}: UseCommentsProps): ExtendedUseQueryResult { + const queryClient = useQueryClient(); + const queryKey = ["comments", sourceType, sourceId, offset, limit, orderBy]; + + const invalidate = () => { + queryClient.invalidateQueries({ + queryKey: queryKey.slice(0, 2), + }); + }; + return { + ...useQuery({ + queryKey, + queryFn: async () => { + return CommentService.commentControllerFindAll({ + sourceId, + sourceType, + orderBy, + limit, + offset, + }); + }, + enabled, + retry: 1, + }), + queryKey, + invalidate, + }; +} diff --git a/src/components/comment/input/ItemCommentsButton.tsx b/src/components/comment/input/ItemCommentsButton.tsx new file mode 100644 index 0000000..9e6e45d --- /dev/null +++ b/src/components/comment/input/ItemCommentsButton.tsx @@ -0,0 +1,49 @@ +import React, { useMemo } from "react"; +import { CreateCommentDto } from "@/wrapper/server"; +import sourceType = CreateCommentDto.sourceType; +import { + useComments, + UseCommentsProps, +} from "@/components/comment/hooks/useComments"; +import { IconMessages } from "@tabler/icons-react"; +import { ActionIcon, Text } from "@mantine/core"; + +interface Props extends Omit { + onClick: () => void; +} + +const ItemCommentsButton = ({ onClick, ...hookProps }: Props) => { + const commentsQuery = useComments({ + orderBy: { + createdAt: "DESC", + }, + enabled: true, + ...hookProps, + }); + + const totalCommentsCount = useMemo(() => { + if ( + commentsQuery.data == undefined || + commentsQuery.data.pagination == undefined + ) { + return 0; + } + + return commentsQuery.data.pagination.totalItems; + }, [commentsQuery.data]); + + return ( + + + {totalCommentsCount} + + ); +}; + +export default ItemCommentsButton; diff --git a/src/components/comment/types.ts b/src/components/comment/types.ts new file mode 100644 index 0000000..53943c1 --- /dev/null +++ b/src/components/comment/types.ts @@ -0,0 +1,3 @@ +import { ReviewComment } from "@/wrapper/server"; + +export type UserComment = ReviewComment; diff --git a/src/components/comment/view/CommentsListItem.tsx b/src/components/comment/view/CommentsListItem.tsx new file mode 100644 index 0000000..7e40e86 --- /dev/null +++ b/src/components/comment/view/CommentsListItem.tsx @@ -0,0 +1,98 @@ +import React, { useMemo, useState } from "react"; +import type { ReviewComment } from "@/wrapper/server"; +import { EditorContent, useEditor } from "@tiptap/react"; +import { COMMENT_EDITOR_EXTENSIONS } from "@/components/comment/editor/CommentEditor"; +import { Box, Divider, Flex, Group, Stack } from "@mantine/core"; +import useOnMobile from "@/components/general/hooks/useOnMobile"; +import { UserAvatarGroup } from "@/components/general/input/UserAvatarGroup"; +import getTimeSinceString from "@/util/getTimeSinceString"; +import ActivityCreateDate from "@/components/activity/item/ActivityCreateDate"; +import CommentsListItemActions from "@/components/comment/view/CommentsListItemActions"; + +interface Props { + comment: ReviewComment; + onEditStart: (commentId: string) => void; + editedCommentId?: string; +} + +const CommentsListItem = ({ comment, onEditStart, editedCommentId }: Props) => { + const [isReadMore, setIsReadMore] = useState(false); + const onMobile = useOnMobile(); + const contentToUse = useMemo(() => { + if (!isReadMore && comment.content.length > 240) { + return comment.content.slice(0, 240) + "..."; + } + + return comment.content; + }, [comment.content, isReadMore]); + + const nonEditableEditor = useEditor( + { + extensions: COMMENT_EDITOR_EXTENSIONS, + editable: false, + content: contentToUse, + }, + [contentToUse], + ); + + const isEditing = + editedCommentId != undefined && editedCommentId === comment.id; + + if (!nonEditableEditor) return; + + return ( + + + + + + + + + + + + + + { + setIsReadMore(!isReadMore); + }} + /> + + + + + + + + ); +}; + +export default CommentsListItem; diff --git a/src/components/comment/view/CommentsListItemActions.tsx b/src/components/comment/view/CommentsListItemActions.tsx new file mode 100644 index 0000000..6b3ef27 --- /dev/null +++ b/src/components/comment/view/CommentsListItemActions.tsx @@ -0,0 +1,88 @@ +import React, { useMemo } from "react"; +import { CommentStatistics, FindOneStatisticsDto } from "@/wrapper/server"; +import { useItemStatistics } from "@/components/statistics/hooks/useItemStatistics"; +import { FindAllCommentsDto } from "@/wrapper/server"; +import { UserComment } from "@/components/comment/types"; +import { ActionIcon, Group, Text } from "@mantine/core"; +import { redirectToAuth } from "supertokens-auth-react"; +import { IconThumbUp } from "@tabler/icons-react"; +import { useUserLike } from "@/components/statistics/hooks/useUserLike"; +import useUserId from "@/components/auth/hooks/useUserId"; +import ItemDropdown from "@/components/general/input/dropdown/ItemDropdown"; +import CommentsRemoveModal from "@/components/comment/view/CommentsRemoveModal"; +import { useDisclosure } from "@mantine/hooks"; + +interface Props { + comment: UserComment; + onEditStart: (commentId: string) => void; +} + +const CommentsListItemActions = ({ comment, onEditStart }: Props) => { + const ownUserId = useUserId(); + const statisticsType = useMemo(() => { + if (comment.reviewId != undefined) { + return FindOneStatisticsDto.sourceType.REVIEW_COMMENT; + } + + return FindOneStatisticsDto.sourceType.REVIEW_COMMENT; + }, [comment]); + + const [removeModalOpened, removeModalUtils] = useDisclosure(); + + const [likesCount, isLiked, toggleUserLike] = useUserLike({ + sourceId: comment.id, + sourceType: statisticsType, + targetUserId: comment.profileUserId, + }); + + const isOwnComment = + ownUserId != undefined && comment.profileUserId === ownUserId; + + return ( + + + { + if (!ownUserId) { + redirectToAuth(); + return; + } + toggleUserLike(); + }} + variant={isLiked ? "filled" : "subtle"} + size={"xl"} + color={isLiked ? "brand" : "white"} + data-disabled={!ownUserId} + > + + {likesCount} + + + + {isOwnComment && ( + <> + { + onEditStart(comment.id); + }} + disabled={!isOwnComment} + /> + { + removeModalUtils.open(); + }} + disabled={!isOwnComment} + /> + + )} + {}} disabled={true} /> + + + ); +}; + +export default CommentsListItemActions; diff --git a/src/components/comment/view/CommentsListView.tsx b/src/components/comment/view/CommentsListView.tsx new file mode 100644 index 0000000..bb803f1 --- /dev/null +++ b/src/components/comment/view/CommentsListView.tsx @@ -0,0 +1,76 @@ +import React, { useMemo, useState } from "react"; +import { + useComments, + UseCommentsProps, +} from "@/components/comment/hooks/useComments"; +import { Pagination, Paper, Stack } from "@mantine/core"; +import CommentsListItem from "@/components/comment/view/CommentsListItem"; +import CenteredErrorMessage from "@/components/general/CenteredErrorMessage"; +import CenteredLoading from "@/components/general/CenteredLoading"; +import GameViewPagination from "@/components/general/view/game/GameViewPagination"; + +interface Props extends Omit { + onEditStart: (commentId: string) => void; + editedCommentId?: string; +} + +const COMMENTS_LIST_VIEW_DEFAULT_LIMIT = 10; + +const CommentsListView = ({ + onEditStart, + editedCommentId, + ...hookProps +}: Props) => { + const [offset, setOffset] = useState(0); + const offsetAsPage = + offset >= COMMENTS_LIST_VIEW_DEFAULT_LIMIT + ? Math.ceil((offset + 1) / COMMENTS_LIST_VIEW_DEFAULT_LIMIT) + : 1; + const commentsQuery = useComments({ + ...hookProps, + offset, + limit: COMMENTS_LIST_VIEW_DEFAULT_LIMIT, + }); + const items = useMemo(() => { + return commentsQuery.data?.data.map((comment) => { + return ( + + ); + }); + }, [commentsQuery.data?.data, editedCommentId, onEditStart]); + + const hasNextPage = + commentsQuery.data != undefined && + commentsQuery.data.pagination.hasNextPage; + + const shouldShowPagination = offsetAsPage !== 1 || hasNextPage; + return ( + + {commentsQuery.isError && ( + + )} + {commentsQuery.isLoading && } + {items} + {shouldShowPagination && ( + { + const pageAsOffset = + COMMENTS_LIST_VIEW_DEFAULT_LIMIT * (page - 1); + setOffset(pageAsOffset); + }} + /> + )} + + ); +}; + +export default CommentsListView; diff --git a/src/components/comment/view/CommentsRemoveModal.tsx b/src/components/comment/view/CommentsRemoveModal.tsx new file mode 100644 index 0000000..207e2be --- /dev/null +++ b/src/components/comment/view/CommentsRemoveModal.tsx @@ -0,0 +1,80 @@ +import React, { useMemo } from "react"; +import { BaseModalProps } from "@/util/types/modal-props"; +import { Button, Group, Modal, Stack, Text } from "@mantine/core"; +import { SessionAuth } from "supertokens-auth-react/recipe/session"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { ApiError, CommentService, FindAllCommentsDto } from "@/wrapper/server"; +import { notifications } from "@mantine/notifications"; +import { UserComment } from "@/components/comment/types"; + +interface Props extends BaseModalProps { + comment: UserComment; +} + +const CommentsRemoveModal = ({ opened, onClose, comment }: Props) => { + const queryClient = useQueryClient(); + + const sourceType = useMemo(() => { + if (comment.reviewId != undefined) { + return FindAllCommentsDto.sourceType.REVIEW; + } + + return FindAllCommentsDto.sourceType.REVIEW; + }, [comment]); + + const sourceId = useMemo(() => { + if (comment.reviewId != undefined) { + return comment.reviewId; + } + + return undefined; + }, [comment]); + + const commentRemoveMutation = useMutation({ + mutationFn: async (commentId: string) => { + return CommentService.commentControllerDelete(commentId, { + sourceType, + }); + }, + onSettled: () => { + queryClient.invalidateQueries({ + queryKey: ["comments", sourceType, sourceId], + }); + }, + onSuccess: () => { + notifications.show({ + color: "green", + message: "Successfully removed your comment!", + }); + }, + }); + + return ( + + + + + + Are you sure you want to remove this comment? + + + + + + + + + + ); +}; + +export default CommentsRemoveModal; diff --git a/src/components/review/view/GameReviewListView.tsx b/src/components/game/info/review/GameInfoReviewList.tsx similarity index 96% rename from src/components/review/view/GameReviewListView.tsx rename to src/components/game/info/review/GameInfoReviewList.tsx index 3307600..2019b5e 100644 --- a/src/components/review/view/GameReviewListView.tsx +++ b/src/components/game/info/review/GameInfoReviewList.tsx @@ -22,7 +22,7 @@ import CenteredLoading from "@/components/general/CenteredLoading"; import CenteredErrorMessage from "@/components/general/CenteredErrorMessage"; import period = FindStatisticsTrendingReviewsDto.period; -interface IGameReviewListViewProps { +interface IGameInfoReviewListProps { gameId: number; } @@ -59,7 +59,7 @@ const queryDtoToSearchParams = (dto: TBasePaginationRequest) => { return searchParams; }; -const GameReviewListView = ({ gameId }: IGameReviewListViewProps) => { +const GameInfoReviewList = ({ gameId }: IGameInfoReviewListProps) => { const onMobile = useOnMobile(); const router = useRouter(); const ownUserId = useUserId(); @@ -125,7 +125,7 @@ const GameReviewListView = ({ gameId }: IGameReviewListViewProps) => { }, [reviewsQuery.data, ownUserId]); if (isLoading) { - return ; + return ; } else if (isError) { return ( { ); }; -export default GameReviewListView; +export default GameInfoReviewList; diff --git a/src/components/game/info/review/GameInfoReviewView.tsx b/src/components/game/info/review/GameInfoReviewScreen.tsx similarity index 62% rename from src/components/game/info/review/GameInfoReviewView.tsx rename to src/components/game/info/review/GameInfoReviewScreen.tsx index 74aa2ac..08e7c26 100644 --- a/src/components/game/info/review/GameInfoReviewView.tsx +++ b/src/components/game/info/review/GameInfoReviewScreen.tsx @@ -1,25 +1,23 @@ import React from "react"; -// Tiptap styles -import "@mantine/tiptap/styles.css"; import { Paper, Stack } from "@mantine/core"; import GameInfoReviewEditorView from "@/components/game/info/review/editor/GameInfoReviewEditorView"; -import GameReviewListView from "@/components/review/view/GameReviewListView"; +import GameInfoReviewList from "@/components/game/info/review/GameInfoReviewList"; interface IGameInfoReviewViewProps { gameId: number; } -const GameInfoReviewView = ({ gameId }: IGameInfoReviewViewProps) => { +const GameInfoReviewScreen = ({ gameId }: IGameInfoReviewViewProps) => { if (!gameId) return null; return ( - + ); }; -export default GameInfoReviewView; +export default GameInfoReviewScreen; diff --git a/src/components/game/info/review/editor/GameInfoReviewEditor.tsx b/src/components/game/info/review/editor/GameInfoReviewEditor.tsx index a322174..43d4f06 100644 --- a/src/components/game/info/review/editor/GameInfoReviewEditor.tsx +++ b/src/components/game/info/review/editor/GameInfoReviewEditor.tsx @@ -21,11 +21,8 @@ const GameInfoReviewEditor = ({ const userId = useUserId(); const reviewQuery = useReviewForUserId(userId, gameId); const previousContent = useMemo(() => { - if (reviewQuery.data != undefined) { - return reviewQuery.data.content; - } - return `Write your review...`; - }, [reviewQuery]); + return reviewQuery.data?.content || ""; + }, [reviewQuery.data]); const editor = useEditor( { @@ -33,12 +30,14 @@ const GameInfoReviewEditor = ({ content: previousContent, onBlur: (e) => { let html = e.editor.getHTML(); - onBlur(html || ""); + onBlur(html ?? ""); }, }, [previousContent], ); + if (!editor) return null; + return ( { }; const NotificationsManager = () => { - const userId = useUserId(); - const infiniteNotificationsQuery = useInfiniteAggregatedNotifications(); + // const userId = useUserId(); + // const infiniteNotificationsQuery = useInfiniteAggregatedNotifications(); // TODO: Check if this is causing trouble + // Update: it was. + // TODO: Update this to use polling instead of SSE. // useEffect(() => { // let eventSource: ReconnectingEventSource; // if (userId) { diff --git a/src/components/general/input/dropdown/ItemDropdown.tsx b/src/components/general/input/dropdown/ItemDropdown.tsx new file mode 100644 index 0000000..b6beaed --- /dev/null +++ b/src/components/general/input/dropdown/ItemDropdown.tsx @@ -0,0 +1,34 @@ +import { ActionIcon, Menu } from "@mantine/core"; +import React, { PropsWithChildren } from "react"; +import { IconDots } from "@tabler/icons-react"; +import ItemDropdownEditButton from "@/components/general/input/dropdown/ItemDropdownEditButton"; +import ItemDropdownRemoveButton from "@/components/general/input/dropdown/ItemDropdownRemoveButton"; +import ItemDropdownReportButton from "@/components/general/input/dropdown/ItemDropdownReportButton"; + +/** + * Common component to build dropdown actions for specific components. + * E.g. the review list items' dropdown. + * @param children + * @constructor + */ +const ItemDropdown = ({ children }: PropsWithChildren) => { + return ( + + + + + + + + Actions + {children} + + + ); +}; + +ItemDropdown.EditButton = ItemDropdownEditButton; +ItemDropdown.RemoveButton = ItemDropdownRemoveButton; +ItemDropdown.ReportButton = ItemDropdownReportButton; + +export default ItemDropdown; diff --git a/src/components/general/input/dropdown/ItemDropdownEditButton.tsx b/src/components/general/input/dropdown/ItemDropdownEditButton.tsx new file mode 100644 index 0000000..d37ad49 --- /dev/null +++ b/src/components/general/input/dropdown/ItemDropdownEditButton.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { IconEdit } from "@tabler/icons-react"; +import { Menu } from "@mantine/core"; +import { ItemDropdownButtonProps } from "@/components/general/input/dropdown/types"; + +interface Props extends ItemDropdownButtonProps {} + +const ItemDropdownEditButton = ({ onClick, disabled = false }: Props) => { + return ( + } + disabled={disabled} + > + Edit + + ); +}; + +export default ItemDropdownEditButton; diff --git a/src/components/general/input/dropdown/ItemDropdownRemoveButton.tsx b/src/components/general/input/dropdown/ItemDropdownRemoveButton.tsx new file mode 100644 index 0000000..2b062a1 --- /dev/null +++ b/src/components/general/input/dropdown/ItemDropdownRemoveButton.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { ItemDropdownButtonProps } from "@/components/general/input/dropdown/types"; +import { IconTrashOff } from "@tabler/icons-react"; +import { Menu } from "@mantine/core"; + +interface Props extends ItemDropdownButtonProps {} + +const ItemDropdownRemoveButton = ({ onClick, disabled }: Props) => { + return ( + } + disabled={disabled} + > + Remove + + ); +}; + +export default ItemDropdownRemoveButton; diff --git a/src/components/general/input/dropdown/ItemDropdownReportButton.tsx b/src/components/general/input/dropdown/ItemDropdownReportButton.tsx new file mode 100644 index 0000000..420e3b0 --- /dev/null +++ b/src/components/general/input/dropdown/ItemDropdownReportButton.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { ItemDropdownButtonProps } from "@/components/general/input/dropdown/types"; +import { IconBan } from "@tabler/icons-react"; +import { Menu } from "@mantine/core"; + +interface Props extends ItemDropdownButtonProps {} + +const ItemDropdownReportButton = ({ onClick, disabled }: Props) => { + return ( + } + disabled={disabled} + > + Report + + ); +}; + +export default ItemDropdownReportButton; diff --git a/src/components/general/input/dropdown/types.ts b/src/components/general/input/dropdown/types.ts new file mode 100644 index 0000000..d9d8f27 --- /dev/null +++ b/src/components/general/input/dropdown/types.ts @@ -0,0 +1,4 @@ +export interface ItemDropdownButtonProps { + onClick: () => void; + disabled?: boolean; +} diff --git a/src/components/general/view/select/GameSelectViewContent.tsx b/src/components/general/view/select/GameSelectViewContent.tsx index a1dde80..1d2ae02 100644 --- a/src/components/general/view/select/GameSelectViewContent.tsx +++ b/src/components/general/view/select/GameSelectViewContent.tsx @@ -7,19 +7,33 @@ import GameSelectViewFigure, { type SelectedProps = Pick< GameSelectViewFigureProps, - "onSelected" | "onDeselected" | "checkIsSelected" + | "onSelected" + | "checkIsSelected" + | "excludeItemsInLibrary" + | "onExcludedItemClick" >; interface Props extends PropsWithChildren { items: TGameOrSearchGame[]; } +/** + * Component similar to GameViewContent that allows users to select items on click. + * @param items + * @param children + * @param checkIsSelected + * @param onSelected + * @param excludeItemsInLibrary + * @param others + * @constructor + */ const GameSelectViewContent = ({ items, children, checkIsSelected, onSelected, - onDeselected, + excludeItemsInLibrary, + onExcludedItemClick, ...others }: Props) => { const columns = useMemo(() => { @@ -33,16 +47,23 @@ const GameSelectViewContent = ({ key={game.id!} checkIsSelected={checkIsSelected} onSelected={onSelected} - onDeselected={onDeselected} game={game} + excludeItemsInLibrary={excludeItemsInLibrary} + onExcludedItemClick={onExcludedItemClick} /> ); }); - }, [checkIsSelected, items, onDeselected, onSelected]); + }, [ + checkIsSelected, + excludeItemsInLibrary, + items, + onExcludedItemClick, + onSelected, + ]); return ( void; - onDeselected: (gameId: number) => void; checkIsSelected: (gameId: number) => boolean; + /** + * If items already on user's library should not be available for selecting + */ + excludeItemsInLibrary: boolean; + /** + * Function to execute when a excluded item is executed + * Depends on 'excludeItemsInLibrary' being true for current item. + */ + onExcludedItemClick?: (gameId: number) => void; } const GameSelectViewFigure = ({ game, checkIsSelected, onSelected, - onDeselected, + excludeItemsInLibrary, + onExcludedItemClick, ...figureProps }: GameSelectViewFigureProps) => { - if (!game) return; + /** + * Passing 'undefined' disables this query + */ + const collectionEntry = useOwnCollectionEntryForGameId( + excludeItemsInLibrary ? game?.id : undefined, + ); - const isSelected = checkIsSelected(game.id!); + const isInCollection = collectionEntry.data != undefined; + + const isExcluded = excludeItemsInLibrary && isInCollection; + + const isSelected = useMemo(() => { + if (!game) return false; + if (isExcluded) return false; + return checkIsSelected(game.id!); + }, [checkIsSelected, game, isExcluded]); + + if (!game) return; return ( {isSelected && ( - + <> + + + )} - {isSelected && ( - + {isExcluded && ( + <> + +
+ + In your library +
+ )}
); diff --git a/src/components/notifications/NotificationSkeleton.tsx b/src/components/notifications/NotificationSkeleton.tsx new file mode 100644 index 0000000..a4de4a0 --- /dev/null +++ b/src/components/notifications/NotificationSkeleton.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { Skeleton } from "@mantine/core"; + +const NotificationSkeleton = () => { + return ; +}; + +export default NotificationSkeleton; diff --git a/src/components/notifications/ReviewAggregatedNotification.tsx b/src/components/notifications/ReviewAggregatedNotification.tsx index 0529b03..d38066e 100644 --- a/src/components/notifications/ReviewAggregatedNotification.tsx +++ b/src/components/notifications/ReviewAggregatedNotification.tsx @@ -1,17 +1,14 @@ import { useMemo } from "react"; -import { - AggregatedNotificationContentProps, - AggregatedNotificationProps, -} from "@/components/notifications/AggregatedNotification"; +import { AggregatedNotificationContentProps } from "@/components/notifications/AggregatedNotification"; import { useReview } from "@/components/review/hooks/useReview"; import { NotificationAggregateDto } from "@/wrapper/server"; import getUniqueProfileNames from "@/components/notifications/utils/getUniqueProfileNames"; -import { Group, Notification, Text } from "@mantine/core"; -import useUserProfile from "@/components/profile/hooks/useUserProfile"; +import { Group, Text } from "@mantine/core"; import { UserAvatar } from "@/components/general/input/UserAvatar"; import Link from "next/link"; -import category = NotificationAggregateDto.category; import { useGame } from "@/components/game/hooks/useGame"; +import NotificationSkeleton from "@/components/notifications/NotificationSkeleton"; +import category = NotificationAggregateDto.category; const ReviewAggregatedNotification = ({ aggregatedNotification, @@ -19,84 +16,52 @@ const ReviewAggregatedNotification = ({ const reviewQuery = useReview(aggregatedNotification.sourceId as string); const gameQuery = useGame(reviewQuery.data?.gameId, {}); - const render = useMemo(() => { - if (aggregatedNotification.notifications.length === 0) { - return null; - } - const profileNames = getUniqueProfileNames( - aggregatedNotification.notifications, - ); - const latestNotification = aggregatedNotification.notifications[0]; - const latestNotificationUserId = latestNotification.profileUserId; - const latestProfileNames = profileNames.slice(0, 2).join(", "); - const hasMoreProfileNames = profileNames.length > 2; - const gameName = gameQuery.data?.name; + const profileNames = useMemo(() => { + return getUniqueProfileNames(aggregatedNotification.notifications); + }, [aggregatedNotification.notifications]); + const latestNotification = aggregatedNotification.notifications[0]; + const latestNotificationUserId = latestNotification.profileUserId; + const latestProfileNames = profileNames.slice(0, 2).join(", "); + const hasMoreProfileNames = profileNames.length > 2; + const gameName = gameQuery.data?.name; + + const actionText = useMemo(() => { switch (aggregatedNotification.category) { case category.LIKE: - return ( - - - {latestNotificationUserId && ( - - )} - - {latestProfileNames}{" "} - {hasMoreProfileNames && ( - <>and {profileNames.length - 2} others - )}{" "} - liked your review - {gameName && ( - <> - {" "} - of {gameName} - - )} - ! - - - - ); + return "liked your review"; case category.COMMENT: - return ( - - - {latestNotificationUserId && ( - - )} - - {latestProfileNames}{" "} - {hasMoreProfileNames && ( - <>and {profileNames.length - 2} others - )}{" "} - commented on your review - {gameName && ( - <> - {" "} - of {gameName} - - )} - ! - - - - ); + return "commented on your review"; } - return null; - }, [ - aggregatedNotification.category, - aggregatedNotification.notifications, - gameQuery.data, - reviewQuery.data?.gameId, - ]); + }, [aggregatedNotification.category]); + + if (reviewQuery.isLoading || gameQuery.isLoading) { + return ; + } - return [render]; + return ( + + + {latestNotificationUserId && ( + + )} + + {latestProfileNames}{" "} + {hasMoreProfileNames && ( + <>and {profileNames.length - 2} others + )}{" "} + {actionText} + {gameName && ( + <> + {" "} + of {gameName} + + )} + ! + + + + ); }; export default ReviewAggregatedNotification; diff --git a/src/components/review/view/UserReviewListView.tsx b/src/components/profile/view/ProfileReviewListView.tsx similarity index 70% rename from src/components/review/view/UserReviewListView.tsx rename to src/components/profile/view/ProfileReviewListView.tsx index 9a18b74..dcc8318 100644 --- a/src/components/review/view/UserReviewListView.tsx +++ b/src/components/profile/view/ProfileReviewListView.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import useOnMobile from "@/components/general/hooks/useOnMobile"; import { useRouter } from "next/router"; import useUserId from "@/components/auth/hooks/useUserId"; @@ -12,26 +12,21 @@ import ReviewListItem from "@/components/review/view/ReviewListItem"; import { Group, Pagination, Stack, Tabs, Text } from "@mantine/core"; import CenteredLoading from "@/components/general/CenteredLoading"; import CenteredErrorMessage from "@/components/general/CenteredErrorMessage"; -import { DEFAULT_GAME_REVIEW_LIST_VIEW_DTO } from "@/components/review/view/GameReviewListView"; +import { DEFAULT_GAME_REVIEW_LIST_VIEW_DTO } from "@/components/game/info/review/GameInfoReviewList"; import { ParsedUrlQuery } from "querystring"; import { TBasePaginationRequest } from "@/util/types/pagination"; import period = FindStatisticsTrendingGamesDto.period; import { DetailsBox } from "@/components/general/DetailsBox"; import GameView from "@/components/general/view/game/GameView"; +import useReviewsForUserId from "@/components/review/hooks/useReviewsForUserId"; const DEFAULT_LIMIT = 7; -export const DEFAULT_USER_REVIEW_LIST_VIEW_DTO: FindStatisticsTrendingReviewsDto = - { - period: period.ALL, +const urlQueryToDto = (query: ParsedUrlQuery): TBasePaginationRequest => { + const dto: TBasePaginationRequest = { offset: 0, limit: DEFAULT_LIMIT, }; - -const urlQueryToDto = (query: ParsedUrlQuery): TBasePaginationRequest => { - const dto: FindStatisticsTrendingReviewsDto = structuredClone( - DEFAULT_USER_REVIEW_LIST_VIEW_DTO, - ); const { page } = query; if (page && typeof page === "string") { const pageInt = parseInt(page, 10); @@ -58,7 +53,7 @@ interface IUserViewListView { userId: string; } -const UserReviewListView = ({ userId }: IUserViewListView) => { +const ProfileReviewListView = ({ userId }: IUserViewListView) => { const onMobile = useOnMobile(); const router = useRouter(); const ownUserId = useUserId(); @@ -66,32 +61,20 @@ const UserReviewListView = ({ userId }: IUserViewListView) => { const [page, setPage] = useState(1); - const trendingReviewsDto = useMemo((): FindStatisticsTrendingReviewsDto => { - const offset = (page - 1) * DEFAULT_LIMIT; - return { - ...DEFAULT_USER_REVIEW_LIST_VIEW_DTO, - offset: offset, - userId: userId, - }; - }, [page, userId]); - const trendingReviewsQuery = useTrendingReviews(trendingReviewsDto); - const trendingReviewsPagination = trendingReviewsQuery.data?.pagination; - - const reviewsIds = trendingReviewsQuery.data?.data.map((s) => s.reviewId!); - const reviewsQuery = useReviews(reviewsIds); + const [offset, setOffset] = useState(0); + const reviewsQuery = useReviewsForUserId(userId, offset, DEFAULT_LIMIT); const isEmpty = - reviewsQuery.data == undefined || reviewsQuery.data.length === 0; - const isLoading = trendingReviewsQuery.isLoading || reviewsQuery.isLoading; - const isError = trendingReviewsQuery.isError || reviewsQuery.isError; + reviewsQuery.data == undefined || reviewsQuery.data.data.length === 0; + const isLoading = reviewsQuery.isLoading; + const isError = reviewsQuery.isError; const handlePagination = (page: number) => { const offset = (page - 1) * DEFAULT_LIMIT; - const updatedDto: FindStatisticsTrendingReviewsDto = { - ...trendingReviewsDto, + const searchParams = queryDtoToSearchParams({ offset, - }; - const searchParams = queryDtoToSearchParams(updatedDto); + limit: DEFAULT_LIMIT, + }); router.replace( { query: searchParams.toString(), @@ -105,13 +88,29 @@ const UserReviewListView = ({ userId }: IUserViewListView) => { }; const items = useMemo(() => { - return reviewsQuery.data?.map((review) => { + return reviewsQuery.data?.data.map((review) => { return ( ); }); }, [reviewsQuery.data]); + /** + * URL to pagination sync effect + */ + useEffect(() => { + if (hasSetInitialQueryParams.current) { + return; + } + + const dto = urlQueryToDto(router.query); + if (dto.offset) { + setOffset(dto.offset); + } + + hasSetInitialQueryParams.current = true; + }, [router.query]); + if (isLoading) { return ; } else if (isError) { @@ -138,7 +137,7 @@ const UserReviewListView = ({ userId }: IUserViewListView) => { {!isEmpty && ( )} @@ -146,4 +145,4 @@ const UserReviewListView = ({ userId }: IUserViewListView) => { ); }; -export default UserReviewListView; +export default ProfileReviewListView; diff --git a/src/components/profile/view/ProfileUserInfo.tsx b/src/components/profile/view/ProfileUserInfo.tsx index f9912c2..6cf82d3 100644 --- a/src/components/profile/view/ProfileUserInfo.tsx +++ b/src/components/profile/view/ProfileUserInfo.tsx @@ -30,7 +30,6 @@ const ProfileUserInfo = ({ userId }: Props) => { const collectionEntriesQuery = useCollectionEntriesForUserId(userId, 0, 1); const reviewsQuery = useReviewsForUserId(userId, 0, 1); const obtainedAchievementsQuery = useAllObtainedAchievements(userId); - const activityQuery = useLatestActivities(userId, 0, 1); const featuredAchievement = useMemo(() => { if (obtainedAchievementsQuery.data == undefined) return null; diff --git a/src/components/review/hooks/useReview.ts b/src/components/review/hooks/useReview.ts index c62054d..e727d89 100644 --- a/src/components/review/hooks/useReview.ts +++ b/src/components/review/hooks/useReview.ts @@ -1,11 +1,15 @@ import { useQuery } from "@tanstack/react-query"; import { ReviewsService } from "@/wrapper/server"; -export function useReview(reviewId: string) { +export function useReview(reviewId: string | undefined) { return useQuery({ queryKey: ["review", reviewId], queryFn: () => { + if (!reviewId) { + return null; + } return ReviewsService.reviewsControllerFindOneById(reviewId); }, + enabled: reviewId != undefined, }); } diff --git a/src/components/review/view/ReviewListItem.tsx b/src/components/review/view/ReviewListItem.tsx index b27ce00..a149690 100644 --- a/src/components/review/view/ReviewListItem.tsx +++ b/src/components/review/view/ReviewListItem.tsx @@ -1,16 +1,23 @@ -import React, { ReactElement, useMemo, useState } from "react"; +import React, { useMemo, useState } from "react"; import { EditorContent, useEditor } from "@tiptap/react"; import { REVIEW_EDITOR_EXTENSIONS } from "@/components/game/info/review/editor/GameInfoReviewEditor"; -import { Box, Divider, Flex, Group, Rating, Stack, Text } from "@mantine/core"; -import { Review } from "@/wrapper/server"; +import { Box, Flex, Group, Stack } from "@mantine/core"; +import { + FindAllCommentsDto, + FindOneStatisticsDto, + Review, +} from "@/wrapper/server"; import useOnMobile from "@/components/general/hooks/useOnMobile"; import useUserId from "@/components/auth/hooks/useUserId"; -import ReviewListItemLikes from "@/components/review/view/ReviewListItemLikes"; -import ReviewListItemDropdown from "@/components/review/view/ReviewListItemDropdown"; +import ReviewListItemDropdownButton from "@/components/review/view/ReviewListItemDropdownButton"; import { UserAvatarGroup } from "@/components/general/input/UserAvatarGroup"; import { useGame } from "@/components/game/hooks/useGame"; import TextLink from "@/components/general/TextLink"; import GameRating from "@/components/general/input/GameRating"; +import ReviewListItemComments from "@/components/review/view/ReviewListItemComments"; +import ItemLikesButton from "@/components/statistics/input/ItemLikesButton"; +import ItemCommentsButton from "@/components/comment/input/ItemCommentsButton"; +import ItemDropdown from "@/components/general/input/dropdown/ItemDropdown"; interface IReviewListViewProps { review: Review; @@ -25,6 +32,7 @@ const ReviewListItem = ({ }: IReviewListViewProps) => { const onMobile = useOnMobile(); const [isReadMore, setIsReadMore] = useState(false); + const [isCommentsOpen, setIsCommentsOpen] = useState(false); const contentToUse = useMemo(() => { if (review != undefined && review.content != undefined) { if (review.content.length < 280 || isReadMore) { @@ -111,16 +119,39 @@ const ReviewListItem = ({
)} - - { + setIsCommentsOpen(!isCommentsOpen); + }} + sourceId={review.id} + sourceType={ + FindAllCommentsDto.sourceType.REVIEW + } + /> + + + + + + + + ); }; diff --git a/src/components/review/view/ReviewListItemComments.tsx b/src/components/review/view/ReviewListItemComments.tsx new file mode 100644 index 0000000..91143d3 --- /dev/null +++ b/src/components/review/view/ReviewListItemComments.tsx @@ -0,0 +1,59 @@ +import React, { useRef, useState } from "react"; +import { CreateCommentDto, FindAllCommentsDto, Review } from "@/wrapper/server"; +import { Space, Stack } from "@mantine/core"; +import CommentsListView from "@/components/comment/view/CommentsListView"; +import CommentEditorView from "@/components/comment/editor/CommentEditorView"; +import sourceType = FindAllCommentsDto.sourceType; +import ItemDropdown from "@/components/general/input/dropdown/ItemDropdown"; + +interface ReviewListItemCommentsProps { + enabled: boolean; + review: Review; +} + +const ReviewListItemComments = ({ + review, + enabled, +}: ReviewListItemCommentsProps) => { + const [editedCommentId, setEditedCommentId] = useState( + undefined, + ); + + const editorContainerRef = useRef(null); + + return ( + + ); +}; + +export default ReviewListItemComments; diff --git a/src/components/review/view/ReviewListItemDropdown.tsx b/src/components/review/view/ReviewListItemDropdown.tsx deleted file mode 100644 index 64c8bbc..0000000 --- a/src/components/review/view/ReviewListItemDropdown.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from "react"; -import { IconBan, IconDots, IconEdit, IconTrashOff } from "@tabler/icons-react"; -import { ActionIcon, Menu } from "@mantine/core"; -import { Review } from "@/wrapper/server"; -import { useDisclosure } from "@mantine/hooks"; -import ReviewListItemRemoveModal from "@/components/review/view/ReviewListItemRemoveModal"; - -interface IReviewListItemDropdownProps { - review: Review; - isOwnReview: boolean; - onEditStart?: () => void; -} - -const ReviewListItemDropdown = ({ - isOwnReview, - review, - onEditStart, -}: IReviewListItemDropdownProps) => { - const [reviewRemoveModalOpened, reviewRemoveModalUtils] = - useDisclosure(false); - return ( - - - - - - - - - Actions - {isOwnReview && ( - <> - } - disabled={!isOwnReview} - > - Edit - - reviewRemoveModalUtils.open()} - leftSection={} - disabled={!isOwnReview} - > - Remove - - - )} - } - disabled={true} - > - Report - - - - ); -}; - -export default ReviewListItemDropdown; diff --git a/src/components/review/view/ReviewListItemDropdownButton.tsx b/src/components/review/view/ReviewListItemDropdownButton.tsx new file mode 100644 index 0000000..aeac952 --- /dev/null +++ b/src/components/review/view/ReviewListItemDropdownButton.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { IconBan, IconDots, IconEdit, IconTrashOff } from "@tabler/icons-react"; +import { ActionIcon, Menu } from "@mantine/core"; +import { Review } from "@/wrapper/server"; +import { useDisclosure } from "@mantine/hooks"; +import ReviewListItemRemoveModal from "@/components/review/view/ReviewListItemRemoveModal"; +import useUserId from "@/components/auth/hooks/useUserId"; +import ItemDropdown from "@/components/general/input/dropdown/ItemDropdown"; + +interface IReviewListItemDropdownProps { + review: Review; + onEditStart?: () => void; +} + +const ReviewListItemDropdownButton = ({ + review, + onEditStart, +}: IReviewListItemDropdownProps) => { + const ownUserId = useUserId(); + const isOwnReview = + ownUserId != undefined && ownUserId === review.profileUserId; + + const [reviewRemoveModalOpened, reviewRemoveModalUtils] = + useDisclosure(false); + + return ( + <> + + + {isOwnReview && ( + <> + { + if (onEditStart) { + onEditStart(); + } + }} + disabled={!onEditStart} + /> + { + reviewRemoveModalUtils.open(); + }} + /> + + )} + {}} disabled={true} /> + + + ); +}; + +export default ReviewListItemDropdownButton; diff --git a/src/components/review/view/ReviewListItemLikes.tsx b/src/components/review/view/ReviewListItemLikes.tsx deleted file mode 100644 index 4943003..0000000 --- a/src/components/review/view/ReviewListItemLikes.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from "react"; -import { IconThumbUp } from "@tabler/icons-react"; -import { ActionIcon, Group, Text } from "@mantine/core"; -import { FindOneStatisticsDto, Review } from "@/wrapper/server"; -import { useUserLike } from "@/components/statistics/hooks/useUserLike"; -import useUserId from "@/components/auth/hooks/useUserId"; -import { redirectToAuth } from "supertokens-auth-react"; - -interface IReviewListLikesProps { - review: Review; -} - -const ReviewListItemLikes = ({ review }: IReviewListLikesProps) => { - const userId = useUserId(); - const [likesCount, isLiked, toggleUserLike] = useUserLike({ - sourceId: review.id, - sourceType: FindOneStatisticsDto.sourceType.REVIEW, - targetUserId: review.profileUserId, - }); - - return ( - - { - if (!userId) { - redirectToAuth(); - return; - } - toggleUserLike(); - }} - variant={isLiked ? "filled" : "subtle"} - size={"xl"} - color={isLiked ? "brand" : "white"} - data-disabled={!userId} - > - - {likesCount} - - - ); -}; - -export default ReviewListItemLikes; diff --git a/src/components/review/view/ReviewListItemRemoveModal.tsx b/src/components/review/view/ReviewListItemRemoveModal.tsx index 5c0bea9..93285d5 100644 --- a/src/components/review/view/ReviewListItemRemoveModal.tsx +++ b/src/components/review/view/ReviewListItemRemoveModal.tsx @@ -33,7 +33,7 @@ const ReviewListItemRemoveModal = ({ reviewId, opened, onClose }: Props) => { diff --git a/src/components/statistics/hooks/useItemStatistics.ts b/src/components/statistics/hooks/useItemStatistics.ts index 6f36345..81fa41c 100644 --- a/src/components/statistics/hooks/useItemStatistics.ts +++ b/src/components/statistics/hooks/useItemStatistics.ts @@ -1,4 +1,6 @@ import { + ActivityStatistics, + CommentStatistics, FindOneStatisticsDto, GameStatistics, ReviewStatistics, @@ -9,7 +11,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { ExtendedUseQueryResult } from "@/util/types/ExtendedUseQueryResult"; export type StatisticsWithStatus = (T extends any - ? GameStatistics | ReviewStatistics + ? GameStatistics | ReviewStatistics | ActivityStatistics | CommentStatistics : T) & StatisticsStatus; diff --git a/src/components/statistics/hooks/useUserLike.ts b/src/components/statistics/hooks/useUserLike.ts index 260419c..89153bd 100644 --- a/src/components/statistics/hooks/useUserLike.ts +++ b/src/components/statistics/hooks/useUserLike.ts @@ -10,7 +10,7 @@ import { } from "@/components/statistics/hooks/useItemStatistics"; import useUserId from "@/components/auth/hooks/useUserId"; -interface IToggleLikeProps { +export interface IToggleLikeProps { targetUserId: string | undefined; sourceId: string | number; sourceType: FindOneStatisticsDto.sourceType; diff --git a/src/components/statistics/input/ItemLikesButton.tsx b/src/components/statistics/input/ItemLikesButton.tsx new file mode 100644 index 0000000..03a99fa --- /dev/null +++ b/src/components/statistics/input/ItemLikesButton.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { + IToggleLikeProps, + useUserLike, +} from "@/components/statistics/hooks/useUserLike"; +import useUserId from "@/components/auth/hooks/useUserId"; +import { ActionIcon, Text } from "@mantine/core"; +import { redirectToAuth } from "supertokens-auth-react"; +import { IconThumbUp } from "@tabler/icons-react"; + +interface Props extends IToggleLikeProps {} + +const ItemLikesButton = ({ targetUserId, sourceType, sourceId }: Props) => { + const userId = useUserId(); + const [likesCount, isLiked, toggleUserLike] = useUserLike({ + sourceId: sourceId, + sourceType: sourceType, + targetUserId: targetUserId, + }); + + return ( + { + if (!userId) { + redirectToAuth(); + return; + } + toggleUserLike(); + }} + variant={isLiked ? "filled" : "subtle"} + size={"xl"} + color={isLiked ? "brand" : "white"} + data-disabled={!userId} + > + + {likesCount} + + ); +}; + +export default ItemLikesButton; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a280328..9437b03 100755 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -21,6 +21,7 @@ import "@mantine/notifications/styles.css"; import "@mantine/dropzone/styles.css"; import "@mantine/carousel/styles.css"; import "@mantine/dates/styles.css"; +import "@mantine/tiptap/styles.css"; /** * Includes tailwind styles diff --git a/src/pages/game/[id].tsx b/src/pages/game/[id].tsx index a088540..2dc8ba8 100755 --- a/src/pages/game/[id].tsx +++ b/src/pages/game/[id].tsx @@ -14,7 +14,7 @@ import { GameRepositoryService, StatisticsActionDto, } from "@/wrapper/server"; -import GameInfoReviewView from "@/components/game/info/review/GameInfoReviewView"; +import GameInfoReviewScreen from "@/components/game/info/review/GameInfoReviewScreen"; import { useUserView } from "@/components/statistics/hooks/useUserView"; import sourceType = FindOneStatisticsDto.sourceType; import Head from "next/head"; @@ -95,7 +95,7 @@ const GameInfoPage = () => { - + diff --git a/src/pages/importer/[type].tsx b/src/pages/importer/[type].tsx index bce96ca..73c47d3 100644 --- a/src/pages/importer/[type].tsx +++ b/src/pages/importer/[type].tsx @@ -127,27 +127,56 @@ function TypePage() { }); }, []); - const handleSelection = (gameId: number, action: "select" | "deselect") => { + const resetSelectedGames = () => { + setValue("selectedGameIds", []); + }; + + const handleSelection = (gameId: number) => { const indexOfElement = selectedGameIds.indexOf(gameId); + const isAlreadyPresent = indexOfElement > -1; - if (action === "deselect") { - if (indexOfElement >= 0) { - const updatedArray = selectedGameIds.toSpliced( - indexOfElement, - 1, - ); - setValue("selectedGameIds", updatedArray); - } - return; - } - // Item is already present in "select" action - if (indexOfElement >= 0) { + if (isAlreadyPresent) { + const updatedArray = selectedGameIds.toSpliced(indexOfElement, 1); + setValue("selectedGameIds", updatedArray); return; } setValue("selectedGameIds", selectedGameIds.concat([gameId])); }; + const removeExcludedItemMutation = useMutation({ + mutationFn: async (gameId: number) => { + const externalGame = importerEntriesQuery.data?.data.find( + (externalGame) => { + return externalGame.gameId === gameId; + }, + ); + + if (!externalGame) { + throw new Error( + "Error while inserting game. Invalid external game ID. Please contact support.", + ); + } + + await ImporterService.importerControllerChangeStatus({ + externalGameId: externalGame.id, + status: "ignored", + }); + + return gameId; + }, + onSuccess: () => { + notifications.show({ + color: "green", + message: `Successfully excluded item already in your library.`, + }); + }, + onSettled: () => { + importerEntriesQuery.invalidate(); + gamesQuery.invalidate(); + }, + }); + const importMutation = useMutation({ mutationFn: async ({ selectedCollectionIds, @@ -196,7 +225,7 @@ function TypePage() { color: "green", message: `Successfully imported ${importedGamesCount} games to your library!`, }); - setValue("selectedGameIds", []); + resetSelectedGames(); }, onSettled: () => { importerEntriesQuery.invalidate(); @@ -278,9 +307,9 @@ function TypePage() { {hasSelectedFinishedGamesCollection && ( Selected games will be marked as "Finished" - because a collection for finished games has - been selected. You can change the finish - date later. + because a collection for finished games is + being used. You can change the finish date + later. )} @@ -318,7 +347,7 @@ function TypePage() { isAllGamesSelected={isAllGamesSelected} onSelectAll={() => { if (isAllGamesSelected) { - setValue("selectedGameIds", []); + resetSelectedGames(); } else if (gameIds) { setValue( "selectedGameIds", @@ -333,12 +362,11 @@ function TypePage() { checkIsSelected={(gameId) => { return selectedGameIds.includes(gameId); }} - onSelected={(gameId) => - handleSelection(gameId, "select") + onSelected={(gameId) => handleSelection(gameId)} + excludeItemsInLibrary={true} + onExcludedItemClick={ + removeExcludedItemMutation.mutate } - onDeselected={(gameId) => { - handleSelection(gameId, "deselect"); - }} > {isLoading && buildLoadingSkeletons()} @@ -351,6 +379,7 @@ function TypePage() { page={page} onPaginationChange={(selectedPage) => { setValue("page", selectedPage); + resetSelectedGames(); }} /> )} diff --git a/src/pages/profile/[userId]/reviews/index.tsx b/src/pages/profile/[userId]/reviews/index.tsx index 7a05a75..34c3c05 100644 --- a/src/pages/profile/[userId]/reviews/index.tsx +++ b/src/pages/profile/[userId]/reviews/index.tsx @@ -1,16 +1,11 @@ import React from "react"; import { useRouter } from "next/router"; -import { useTrendingReviews } from "@/components/statistics/hooks/useTrendingReviews"; -import { FindStatisticsTrendingReviewsDto } from "@/wrapper/server"; -import period = FindStatisticsTrendingReviewsDto.period; -import { Box, Container, Paper } from "@mantine/core"; +import { Container, Paper } from "@mantine/core"; import { DetailsBox } from "@/components/general/DetailsBox"; import useUserProfile from "@/components/profile/hooks/useUserProfile"; import CenteredLoading from "@/components/general/CenteredLoading"; -import GameReviewListView from "@/components/review/view/GameReviewListView"; -import UserReviewListView, { - DEFAULT_USER_REVIEW_LIST_VIEW_DTO, -} from "@/components/review/view/UserReviewListView"; + +import ProfileReviewListView from "@/components/profile/view/ProfileReviewListView"; const Index = () => { const router = useRouter(); @@ -35,7 +30,7 @@ const Index = () => { }} > - + diff --git a/src/wrapper/input/server_swagger.json b/src/wrapper/input/server_swagger.json index fcd07b8..6e41b33 100644 --- a/src/wrapper/input/server_swagger.json +++ b/src/wrapper/input/server_swagger.json @@ -1 +1 @@ -{"openapi":"3.0.0","paths":{"/v1/auth/logout":{"get":{"operationId":"AuthController_logout","parameters":[],"responses":{"200":{"description":""}},"tags":["auth"]}},"/v1/libraries":{"get":{"operationId":"LibrariesController_findOwn","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Library"}}}}},"tags":["libraries"]}},"/v1/libraries/{id}":{"get":{"operationId":"LibrariesController_findOneByIdWithPermissions","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Library"}}}}},"tags":["libraries"]}},"/v1/collections/{id}":{"get":{"operationId":"CollectionsController_findOneByIdWithPermissions","summary":"","description":"Returns a collection which the user has access to\n\n(Either its own collection or a public one)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collection"}}}}},"tags":["collections"]},"patch":{"operationId":"CollectionsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCollectionDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["collections"]},"delete":{"operationId":"CollectionsController_delete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["collections"]}},"/v1/collections/library/{userId}":{"get":{"operationId":"CollectionsController_findAllByUserIdWithPermissions","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}}}}}},"tags":["collections"]}},"/v1/collections":{"post":{"operationId":"CollectionsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCollectionDto"}}}},"responses":{"201":{"description":""}},"tags":["collections"]}},"/v1/reviews":{"post":{"operationId":"ReviewsController_createOrUpdate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReviewDto"}}}},"responses":{"201":{"description":""}},"tags":["reviews"]},"get":{"operationId":"ReviewsController_findOneByUserIdAndGameId","parameters":[{"name":"id","required":true,"in":"query","schema":{"type":"string"}},{"name":"gameId","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}}},"tags":["reviews"]}},"/v1/reviews/all":{"post":{"operationId":"ReviewsController_findAllById","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindAllReviewsByIdDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Review"}}}}}},"tags":["reviews"]}},"/v1/reviews/score":{"get":{"operationId":"ReviewsController_getScoreForGameId","parameters":[{"name":"gameId","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewScoreResponseDto"}}}}},"tags":["reviews"]}},"/v1/reviews/profile/{userId}":{"get":{"operationId":"ReviewsController_findAllByUserId","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindReviewPaginatedDto"}}}}},"tags":["reviews"]}},"/v1/reviews/game/{id}":{"get":{"operationId":"ReviewsController_findAllByGameId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindReviewPaginatedDto"}}}}},"tags":["reviews"]}},"/v1/reviews/{id}":{"get":{"operationId":"ReviewsController_findOneById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}}},"tags":["reviews"]},"delete":{"operationId":"ReviewsController_delete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["reviews"]}},"/v1/profile":{"patch":{"operationId":"ProfileController_update","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/UpdateProfileDto"}}}},"responses":{"200":{"description":""}},"tags":["profile"]},"get":{"operationId":"ProfileController_findOwn","summary":"","description":"Used to access own profile","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}}},"tags":["profile"]}},"/v1/profile/{id}":{"get":{"operationId":"ProfileController_findOneById","summary":"","description":"Used to access other users' profiles","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}}},"tags":["profile"]}},"/v1/activities":{"get":{"operationId":"ActivitiesRepositoryController_findLatest","parameters":[{"name":"userId","required":false,"in":"query","schema":{"minLength":36,"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivitiesPaginatedResponseDto"}}}}},"tags":["activities"]}},"/v1/statistics/queue/like":{"post":{"operationId":"StatisticsQueueController_addLike","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsActionDto"}}}},"responses":{"201":{"description":""}},"tags":["statistics-queue"]},"delete":{"operationId":"StatisticsQueueController_removeLike","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsActionDto"}}}},"responses":{"200":{"description":""}},"tags":["statistics-queue"]}},"/v1/statistics/queue/view":{"post":{"operationId":"StatisticsQueueController_addView","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsActionDto"}}}},"responses":{"201":{"description":""}},"tags":["statistics-queue"]}},"/v1/statistics":{"post":{"operationId":"StatisticsController_findOneBySourceIdAndType","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindOneStatisticsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["statistics"]}},"/v1/statistics/trending/games":{"post":{"operationId":"StatisticsController_findTrendingGames","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindStatisticsTrendingGamesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameStatisticsPaginatedResponseDto"}}}}},"tags":["statistics"]}},"/v1/statistics/trending/reviews":{"post":{"operationId":"StatisticsController_findTrendingReviews","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindStatisticsTrendingReviewsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewStatisticsPaginatedResponseDto"}}}}},"tags":["statistics"]}},"/v1/statistics/trending/activities":{"post":{"operationId":"StatisticsController_findTrendingActivities","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindStatisticsTrendingActivitiesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewStatisticsPaginatedResponseDto"}}}}},"tags":["statistics"]}},"/v1/statistics/status":{"get":{"operationId":"StatisticsController_getStatus","parameters":[{"name":"statisticsId","required":true,"in":"query","schema":{"type":"number"}},{"name":"sourceType","required":true,"in":"query","schema":{"enum":["game","review","activity"],"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsStatus"}}}}},"tags":["statistics"]}},"/v1/notifications":{"get":{"operationId":"NotificationsController_findAllAndAggregate","parameters":[{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedNotificationAggregationDto"}}}}},"tags":["notifications"]}},"/v1/notifications/{id}/view":{"put":{"operationId":"NotificationsController_updateViewedStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationViewUpdateDto"}}}},"responses":{"200":{"description":""}},"tags":["notifications"]}},"/v1/game/repository/resource":{"get":{"operationId":"GameRepositoryController_getResource","parameters":[{"name":"resourceName","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["game-repository"]}},"/v1/game/repository/{id}/platforms/icon":{"get":{"operationId":"GameRepositoryController_getIconNamesForPlatformAbbreviations","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["game-repository"]}},"/v1/game/repository/{id}/external-stores":{"get":{"operationId":"GameRepositoryController_getExternalStoresForGameId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameExternalStoreDto"}}}}}},"tags":["game-repository"]}},"/v1/game/repository/{id}":{"post":{"operationId":"GameRepositoryController_findOneById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameRepositoryFindOneDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Game"}}}}},"tags":["game-repository"]}},"/v1/game/repository":{"post":{"operationId":"GameRepositoryController_findAllByIds","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameRepositoryFindAllDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}}}}},"tags":["game-repository"]}},"/v1/sync/hltb/{gameId}":{"get":{"operationId":"HltbController_findPlaytimeForGameId","parameters":[{"name":"gameId","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GamePlaytime"}}}}},"tags":["sync-hltb"]}},"/v1/achievements":{"get":{"operationId":"AchievementsController_getAchievements","parameters":[{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedAchievementsResponseDto"}}}}},"tags":["achievements"]}},"/v1/achievements/obtained/{id}":{"get":{"operationId":"AchievementsController_getObtainedAchievement","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"targetUserId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObtainedAchievement"}}}}},"tags":["achievements"]}},"/v1/achievements/obtained":{"get":{"operationId":"AchievementsController_getAllObtainedAchievements","parameters":[{"name":"targetUserId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ObtainedAchievement"}}}}}},"tags":["achievements"]}},"/v1/achievements/obtained/{id}/featured":{"put":{"operationId":"AchievementsController_updateFeaturedObtainedAchievement","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFeaturedObtainedAchievementDto"}}}},"responses":{"200":{"description":""}},"tags":["achievements"]}},"/v1/level/{userId}":{"get":{"operationId":"LevelController_findOne","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserLevel"}}}}},"tags":["level"]}},"/v1/collections-entries":{"post":{"operationId":"CollectionsEntriesController_createOrUpdate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUpdateCollectionEntryDto"}}}},"responses":{"201":{"description":""}},"tags":["collections-entries"]}},"/v1/collections-entries/{id}":{"get":{"operationId":"CollectionsEntriesController_findEntryById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntry"}}}}},"tags":["collections-entries"]},"delete":{"operationId":"CollectionsEntriesController_deleteOwnEntry","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["collections-entries"]}},"/v1/collections-entries/game/{id}":{"get":{"operationId":"CollectionsEntriesController_findOwnEntryByGameId","summary":"","description":"Returns a specific collection entry based on game ID","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntry"}}}},"400":{"description":"Invalid query"}},"tags":["collections-entries"]}},"/v1/collections-entries/{id}/platforms/icons":{"get":{"operationId":"CollectionsEntriesController_getIconsForOwnedPlatforms","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["collections-entries"]}},"/v1/collections-entries/game/{id}/favorite":{"post":{"operationId":"CollectionsEntriesController_changeFavoriteStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFavoriteStatusCollectionEntryDto"}}}},"responses":{"204":{"description":""}},"tags":["collections-entries"]}},"/v1/collections-entries/library/{id}":{"get":{"operationId":"CollectionsEntriesController_findAllByLibraryId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntriesPaginatedResponseDto"}}}}},"tags":["collections-entries"]}},"/v1/collections-entries/library/{id}/favorites":{"get":{"operationId":"CollectionsEntriesController_findFavoritesByLibraryId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntriesPaginatedResponseDto"}}}}},"tags":["collections-entries"]}},"/v1/collections-entries/collection/{id}":{"post":{"operationId":"CollectionsEntriesController_findAllByCollectionId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindCollectionEntriesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntriesPaginatedResponseDto"}}}}},"tags":["collections-entries"]}},"/v1/health":{"get":{"operationId":"HealthController_health","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["health"]}},"/v1/activities/feed":{"get":{"operationId":"ActivitiesFeedController_buildActivitiesFeed","parameters":[{"name":"criteria","required":true,"in":"query","schema":{"enum":["following","all"],"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivitiesFeedPaginatedResponseDto"}}}}},"tags":["activities-feed"]}},"/v1/follow/status":{"get":{"operationId":"FollowController_getFollowerStatus","parameters":[{"name":"followerUserId","required":true,"in":"query","schema":{"type":"string"}},{"name":"followedUserId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowStatusDto"}}}}},"tags":["follow"]}},"/v1/follow/{id}":{"get":{"operationId":"FollowController_getUserFollowById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserFollow"}}}}},"tags":["follow"]}},"/v1/follow/info":{"post":{"operationId":"FollowController_getFollowInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowInfoRequestDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowInfoResponseDto"}}}}},"tags":["follow"]}},"/v1/follow":{"post":{"operationId":"FollowController_registerFollow","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowRegisterDto"}}}},"responses":{"201":{"description":""}},"tags":["follow"]},"delete":{"operationId":"FollowController_removeFollow","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowRemoveDto"}}}},"responses":{"200":{"description":""}},"tags":["follow"]}},"/v1/importer/{source}":{"get":{"operationId":"ImporterController_findUnprocessedEntries","parameters":[{"name":"source","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImporterPaginatedResponseDto"}}}}},"tags":["importer"]}},"/v1/importer/status":{"post":{"operationId":"ImporterController_changeStatus","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImporterStatusUpdateRequestDto"}}}},"responses":{"201":{"description":""}},"tags":["importer"]}},"/v1/connections":{"get":{"operationId":"ConnectionsController_findAvailableConnections","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FindAvailableConnectionsResponseDto"}}}}}},"tags":["connections"]},"post":{"operationId":"ConnectionsController_createOrUpdate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionCreateDto"}}}},"responses":{"201":{"description":""}},"tags":["connections"]}},"/v1/connections/own":{"get":{"operationId":"ConnectionsController_findOwn","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserConnection"}}}}}},"tags":["connections"]}},"/v1/connections/own/{type}":{"get":{"operationId":"ConnectionsController_findOwnByType","parameters":[{"name":"type","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConnection"}}}}},"tags":["connections"]}},"/v1/connections/{id}":{"delete":{"operationId":"ConnectionsController_delete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["connections"]}}},"info":{"title":"GameNode API","description":"API docs for the videogame catalog system GameNode.

Built with love by the GameNode team.","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"schemas":{"Library":{"type":"object","properties":{"userId":{"type":"string","description":"@description The primary key of the library entity.\nAlso used to share the library with other users.\n\nSame as SuperTokens' userId."},"collections":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["userId","collections","createdAt","updatedAt"]},"GameCover":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["game","id"]},"GameCollection":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"slug":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"checksum":{"type":"string"},"url":{"type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}},"required":["id","name","slug","createdAt","updatedAt","checksum","url","games"]},"GameAlternativeName":{"type":"object","properties":{"id":{"type":"number"},"comment":{"type":"string"},"name":{"type":"string"},"checksum":{"type":"string"},"game":{"$ref":"#/components/schemas/Game"}},"required":["id","game"]},"GameArtwork":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["game","id"]},"GameScreenshot":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["game","id"]},"GameLocalization":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["game","id","createdAt","updatedAt"]},"GameMode":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["game","id","createdAt","updatedAt"]},"GameGenre":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["games","id","createdAt","updatedAt"]},"GameTheme":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","createdAt","updatedAt"]},"GamePlayerPerspective":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["games","id","createdAt","updatedAt"]},"GameEngineLogo":{"type":"object","properties":{"engine":{"$ref":"#/components/schemas/GameEngine"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["engine","id"]},"GameCompanyLogo":{"type":"object","properties":{"company":{"$ref":"#/components/schemas/GameCompany"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["company","id"]},"GameCompany":{"type":"object","properties":{"id":{"type":"number"},"changeDate":{"format":"date-time","type":"string"},"changeDateCategory":{"type":"string"},"changedCompany":{"$ref":"#/components/schemas/GameCompany"},"checksum":{"type":"string"},"country":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"logo":{"$ref":"#/components/schemas/GameCompanyLogo"},"name":{"type":"string"},"parent":{"$ref":"#/components/schemas/GameCompany"},"slug":{"type":"string"},"startDate":{"format":"date-time","type":"string"},"startDateCategory":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"url":{"type":"string"}},"required":["id","createdAt","name","slug","updatedAt"]},"GamePlatform":{"type":"object","properties":{"id":{"type":"number"},"abbreviation":{"type":"string"},"alternative_name":{"type":"string"},"category":{"type":"number","enum":[1,2,3,4,5,6]},"checksum":{"type":"string"},"generation":{"type":"number"},"name":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"collectionEntries":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEntry"}}},"required":["id","abbreviation","alternative_name","category","checksum","generation","name","createdAt","updatedAt","games","collectionEntries"]},"GameEngine":{"type":"object","properties":{"logo":{"$ref":"#/components/schemas/GameEngineLogo"},"companies":{"type":"array","items":{"$ref":"#/components/schemas/GameCompany"}},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/GamePlatform"}},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["logo","companies","platforms","games","id","createdAt","updatedAt"]},"GameKeyword":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["game","id","createdAt","updatedAt"]},"GameFranchise":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["games","id","createdAt","updatedAt"]},"GameExternalGame":{"type":"object","properties":{"id":{"type":"number"},"uid":{"type":"string","description":"Corresponds to the game id on the target source (see GameExternalGameCategory).\nIt's called uid, not uuid."},"category":{"type":"number","enum":[1,5,10,11,13,14,15,20,22,23,26,28,29,30,31,32,36,37,54,55]},"media":{"type":"number","enum":[1,2]},"checksum":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"year":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"}},"required":["id","uid","createdAt","updatedAt","game","gameId"]},"GameInvolvedCompany":{"type":"object","properties":{"id":{"type":"number"},"checksum":{"type":"string"},"company":{"$ref":"#/components/schemas/GameCompany"},"companyId":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"developer":{"type":"boolean"},"porting":{"type":"boolean"},"publisher":{"type":"boolean"},"supporting":{"type":"boolean"},"updatedAt":{"format":"date-time","type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}},"required":["id","company","companyId","createdAt","developer","porting","publisher","supporting","updatedAt","games"]},"Game":{"type":"object","properties":{"id":{"type":"number","description":"Should be mapped to the IGDB ID of the game."},"name":{"type":"string"},"slug":{"type":"string"},"aggregatedRating":{"type":"number"},"aggregatedRatingCount":{"type":"number"},"category":{"enum":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"type":"number"},"status":{"enum":[0,2,3,4,5,6,7,8],"type":"number"},"summary":{"type":"string"},"storyline":{"type":"string"},"checksum":{"type":"string"},"url":{"type":"string"},"firstReleaseDate":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"dlcs":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"dlcOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expansions":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expansionOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expandedGames":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expandedGameOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"similarGames":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"similarGameOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remakes":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remakeOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remasters":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remasterOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"cover":{"$ref":"#/components/schemas/GameCover"},"collection":{"$ref":"#/components/schemas/GameCollection"},"alternativeNames":{"type":"array","items":{"$ref":"#/components/schemas/GameAlternativeName"}},"artworks":{"type":"array","items":{"$ref":"#/components/schemas/GameArtwork"}},"screenshots":{"type":"array","items":{"$ref":"#/components/schemas/GameScreenshot"}},"gameLocalizations":{"type":"array","items":{"$ref":"#/components/schemas/GameLocalization"}},"gameModes":{"type":"array","items":{"$ref":"#/components/schemas/GameMode"}},"genres":{"type":"array","items":{"$ref":"#/components/schemas/GameGenre"}},"themes":{"type":"array","items":{"$ref":"#/components/schemas/GameTheme"}},"playerPerspectives":{"type":"array","items":{"$ref":"#/components/schemas/GamePlayerPerspective"}},"gameEngines":{"type":"array","items":{"$ref":"#/components/schemas/GameEngine"}},"keywords":{"type":"array","items":{"$ref":"#/components/schemas/GameKeyword"}},"franchises":{"type":"array","items":{"$ref":"#/components/schemas/GameFranchise"}},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/GamePlatform"}},"externalGames":{"type":"array","items":{"$ref":"#/components/schemas/GameExternalGame"}},"involvedCompanies":{"type":"array","items":{"$ref":"#/components/schemas/GameInvolvedCompany"}},"source":{"type":"string","description":"Oh dear maintainer, please forgive me for using transient fields.","default":"MYSQL","enum":["MYSQL","MANTICORE"]}},"required":["id","name","slug","category","status","summary","storyline","checksum","url","firstReleaseDate","createdAt","updatedAt","involvedCompanies","source"]},"ProfileAvatar":{"type":"object","properties":{"id":{"type":"number"},"mimetype":{"type":"string"},"extension":{"type":"string"},"size":{"type":"number"},"filename":{"type":"string"},"encoding":{"type":"string"},"profile":{"$ref":"#/components/schemas/Profile"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","mimetype","extension","size","filename","encoding","profile","createdAt","updatedAt"]},"UserFollow":{"type":"object","properties":{"id":{"type":"number"},"follower":{"$ref":"#/components/schemas/Profile"},"followerUserId":{"type":"string"},"followed":{"$ref":"#/components/schemas/Profile"},"followedUserId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","follower","followerUserId","followed","followedUserId","createdAt","updatedAt"]},"Profile":{"type":"object","properties":{"userId":{"type":"string","description":"Shareable string ID\n\nSame as SuperTokens' userId."},"username":{"type":"string"},"bio":{"type":"string"},"avatar":{"$ref":"#/components/schemas/ProfileAvatar"},"followers":{"type":"array","items":{"$ref":"#/components/schemas/UserFollow"}},"following":{"type":"array","items":{"$ref":"#/components/schemas/UserFollow"}},"usernameLastUpdatedAt":{"format":"date-time","type":"string","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["userId","username","bio","avatar","followers","following","usernameLastUpdatedAt","createdAt","updatedAt"]},"Review":{"type":"object","properties":{"id":{"type":"string"},"content":{"type":"string","nullable":true},"rating":{"type":"number"},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"collectionEntry":{"$ref":"#/components/schemas/CollectionEntry"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","content","rating","game","gameId","profile","profileUserId","collectionEntry","createdAt","updatedAt"]},"CollectionEntry":{"type":"object","properties":{"id":{"type":"string"},"collections":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"},"ownedPlatforms":{"description":"The platforms on which the user owns the game.","type":"array","items":{"$ref":"#/components/schemas/GamePlatform"}},"review":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Review"}]},"isFavorite":{"type":"boolean"},"finishedAt":{"format":"date-time","type":"string","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","collections","game","gameId","ownedPlatforms","review","isFavorite","finishedAt","createdAt","updatedAt"]},"Collection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"isPublic":{"type":"boolean"},"library":{"$ref":"#/components/schemas/Library"},"libraryUserId":{"type":"string"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEntry"}},"isFeatured":{"type":"boolean"},"isFinished":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","name","description","isPublic","library","libraryUserId","entries","isFeatured","isFinished","createdAt","updatedAt"]},"CreateCollectionDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"isPublic":{"type":"boolean"},"isFeatured":{"type":"boolean"},"isFinished":{"type":"boolean"}},"required":["name","isPublic","isFeatured","isFinished"]},"UpdateCollectionDto":{"type":"object","properties":{}},"CreateReviewDto":{"type":"object","properties":{"gameId":{"type":"number"},"content":{"type":"string","minLength":20},"rating":{"type":"number","minimum":0,"maximum":5}},"required":["gameId","content","rating"]},"FindAllReviewsByIdDto":{"type":"object","properties":{"reviewsIds":{"type":"array","items":{"type":"string"}}},"required":["reviewsIds"]},"ReviewScoreDistribution":{"type":"object","properties":{"1":{"type":"number"},"2":{"type":"number"},"3":{"type":"number"},"4":{"type":"number"},"5":{"type":"number"},"total":{"type":"number","description":"Total number of reviews"}},"required":["1","2","3","4","5","total"]},"ReviewScoreResponseDto":{"type":"object","properties":{"median":{"type":"number"},"distribution":{"$ref":"#/components/schemas/ReviewScoreDistribution"}},"required":["median","distribution"]},"PaginationInfo":{"type":"object","properties":{"totalItems":{"type":"number","description":"Total number of items available for the current query"},"totalPages":{"type":"number","description":"Total number of pages available for the current query"},"hasNextPage":{"type":"boolean","description":"If this query allows for a next page"}},"required":["totalItems","totalPages","hasNextPage"]},"FindReviewPaginatedDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"UpdateProfileDto":{"type":"object","properties":{"username":{"type":"string","minLength":4,"maxLength":20},"avatar":{"type":"object"},"bio":{"type":"string","minLength":1,"maxLength":240}}},"Activity":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["REVIEW","FOLLOW","COLLECTION_ENTRY"]},"profile":{"description":"The associated profile with this Activity (e.g. user who performed an action)","allOf":[{"$ref":"#/components/schemas/Profile"}]},"profileUserId":{"type":"string"},"collectionEntry":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/CollectionEntry"}]},"collectionEntryId":{"type":"string","nullable":true},"collection":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Collection"}]},"collectionId":{"type":"string","nullable":true},"review":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Review"}]},"reviewId":{"type":"string","nullable":true},"userFollow":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/UserFollow"}]},"userFollowId":{"type":"number","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","type","profile","profileUserId","collectionEntry","collectionEntryId","collection","collectionId","review","reviewId","userFollow","userFollowId","createdAt","updatedAt"]},"ActivitiesPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Activity"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"StatisticsActionDto":{"type":"object","properties":{"sourceId":{"oneOf":[{"type":"string"},{"type":"number"}]},"targetUserId":{"type":"string","minLength":36},"sourceType":{"enum":["game","review","activity"],"type":"string"}},"required":["sourceId","sourceType"]},"FindOneStatisticsDto":{"type":"object","properties":{"sourceId":{"oneOf":[{"type":"string"},{"type":"number"}]},"sourceType":{"type":"string","enum":["game","review","activity"]}},"required":["sourceId","sourceType"]},"GameRepositoryFilterDto":{"type":"object","properties":{"ids":{"description":"If this is supplied, filtering will be done only for entities specified here.
\nUseful to filter data received from entities which hold game ids (like GameStatistics, Reviews, etc.)","type":"array","items":{"type":"number"}},"status":{"type":"number","enum":[0,2,3,4,5,6,7,8]},"category":{"type":"number","enum":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]},"themes":{"type":"array","items":{"type":"number"}},"gameModes":{"type":"array","items":{"type":"number"}},"platforms":{"type":"array","items":{"type":"number"}},"genres":{"type":"array","items":{"type":"number"}},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}}},"FindStatisticsTrendingGamesDto":{"type":"object","properties":{"criteria":{"$ref":"#/components/schemas/GameRepositoryFilterDto"},"period":{"type":"string","enum":["day","week","month","quarter","half_year","year","all"]},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}},"required":["period"]},"ActivityStatistics":{"type":"object","properties":{"views":{"type":"array","items":{"$ref":"#/components/schemas/UserView"}},"likes":{"type":"array","items":{"$ref":"#/components/schemas/UserLike"}},"activity":{"$ref":"#/components/schemas/Activity"},"activityId":{"type":"string"},"id":{"type":"number"},"viewsCount":{"type":"number"},"likesCount":{"type":"number"}},"required":["views","likes","activity","activityId","id","viewsCount","likesCount"]},"UserLike":{"type":"object","properties":{"id":{"type":"number"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"gameStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/GameStatistics"}]},"gameStatisticsId":{"type":"number","nullable":true},"reviewStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ReviewStatistics"}]},"reviewStatisticsId":{"type":"number","nullable":true},"activityStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ActivityStatistics"}]},"activityStatisticsId":{"type":"number","nullable":true}},"required":["id","profile","profileUserId","createdAt","updatedAt","gameStatistics","gameStatisticsId","reviewStatistics","reviewStatisticsId","activityStatistics","activityStatisticsId"]},"ReviewStatistics":{"type":"object","properties":{"views":{"type":"array","items":{"$ref":"#/components/schemas/UserView"}},"likes":{"type":"array","items":{"$ref":"#/components/schemas/UserLike"}},"review":{"$ref":"#/components/schemas/Review"},"reviewId":{"type":"string"},"id":{"type":"number"},"viewsCount":{"type":"number"},"likesCount":{"type":"number"}},"required":["views","likes","review","reviewId","id","viewsCount","likesCount"]},"UserView":{"type":"object","properties":{"id":{"type":"number"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"gameStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/GameStatistics"}]},"reviewStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ReviewStatistics"}]},"activityStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ActivityStatistics"}]}},"required":["id","profileUserId","createdAt","updatedAt","gameStatistics","reviewStatistics","activityStatistics"]},"GameStatistics":{"type":"object","properties":{"views":{"type":"array","items":{"$ref":"#/components/schemas/UserView"}},"likes":{"type":"array","items":{"$ref":"#/components/schemas/UserLike"}},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"},"id":{"type":"number"},"viewsCount":{"type":"number"},"likesCount":{"type":"number"}},"required":["views","likes","game","gameId","id","viewsCount","likesCount"]},"GameStatisticsPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GameStatistics"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FindStatisticsTrendingReviewsDto":{"type":"object","properties":{"reviewId":{"type":"string","description":"Usually, this property should not be used unless a specific review needs to be retrieved, and it's easier to just\ncall the statistics controller."},"gameId":{"type":"number"},"userId":{"type":"string","minLength":36},"period":{"type":"string","enum":["day","week","month","quarter","half_year","year","all"]},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}},"required":["period"]},"ReviewStatisticsPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ReviewStatistics"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FindStatisticsTrendingActivitiesDto":{"type":"object","properties":{"activityId":{"type":"string","description":"Usually, this property should not be used unless a specific activity needs to be retrieved, and it's easier to just\ncall the statistics controller.","minLength":36},"userId":{"type":"string","minLength":36},"activityType":{"type":"string","enum":["REVIEW","FOLLOW","COLLECTION_ENTRY"]},"period":{"type":"string","enum":["day","week","month","quarter","half_year","year","all"]},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}},"required":["period"]},"StatisticsStatus":{"type":"object","properties":{"isLiked":{"type":"boolean"},"isViewed":{"type":"boolean"}},"required":["isLiked","isViewed"]},"Notification":{"type":"object","properties":{"id":{"type":"number"},"sourceType":{"type":"string","enum":["game","review","activity","profile"]},"category":{"type":"string","description":"What this notification's about. E.g.: a new like, a new follower, a game launch, etc.","enum":["follow","like","comment","launch"]},"review":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Review"}]},"reviewId":{"type":"string","nullable":true},"game":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Game"}]},"gameId":{"type":"number","nullable":true},"activity":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Activity"}]},"activityId":{"type":"string","nullable":true},"profile":{"nullable":true,"description":"User responsible for generating this notification (e.g. user that liked a review).","allOf":[{"$ref":"#/components/schemas/Profile"}]},"profileUserId":{"type":"string","nullable":true,"description":"User responsible for generating this notification (e.g. user that liked a review).\nWhen null/undefined, the notification was generated by the 'system'."},"isViewed":{"type":"boolean"},"targetProfile":{"nullable":true,"description":"User which is the target for this notification.
\nIf this is empty (null/undefined), the notification is targeted at all users.
\nNot to be confused with the 'profile' property.","allOf":[{"$ref":"#/components/schemas/Profile"}]},"targetProfileUserId":{"type":"string","nullable":true,"description":"User which is the target for this notification.
\nIf this is empty (null/undefined), the notification is targeted at all users.
\nNot to be confused with the 'profile' property."},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","sourceType","category","review","reviewId","game","gameId","activity","activityId","profile","profileUserId","isViewed","targetProfile","targetProfileUserId","createdAt","updatedAt"]},"NotificationAggregateDto":{"type":"object","properties":{"sourceId":{"oneOf":[{"type":"string"},{"type":"number"}]},"category":{"type":"string","enum":["follow","like","comment","launch"]},"sourceType":{"type":"string","enum":["game","review","activity","profile"]},"notifications":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}}},"required":["sourceId","category","sourceType","notifications"]},"PaginatedNotificationAggregationDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NotificationAggregateDto"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"NotificationViewUpdateDto":{"type":"object","properties":{"isViewed":{"type":"boolean"}},"required":["isViewed"]},"GameExternalStoreDto":{"type":"object","properties":{"icon":{"type":"string","nullable":true,"description":"Icon representing said store/service."},"storeName":{"type":"string","nullable":true},"id":{"type":"number"},"uid":{"type":"string","description":"Corresponds to the game id on the target source (see GameExternalGameCategory).\nIt's called uid, not uuid."},"category":{"type":"number","enum":[1,5,10,11,13,14,15,20,22,23,26,28,29,30,31,32,36,37,54,55]},"media":{"type":"number","enum":[1,2]},"checksum":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"year":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"gameId":{"type":"number"}},"required":["icon","storeName","id","uid","createdAt","updatedAt","gameId"]},"GameRepositoryFindOneDto":{"type":"object","properties":{"relations":{"type":"object"}}},"GameRepositoryFindAllDto":{"type":"object","properties":{"gameIds":{"type":"array","items":{"type":"number"}},"relations":{"type":"object"}},"required":["gameIds"]},"GamePlaytime":{"type":"object","properties":{"id":{"type":"number"},"gameId":{"type":"number"},"game":{"$ref":"#/components/schemas/Game"},"sourceId":{"type":"number"},"timeMain":{"type":"number","nullable":true},"timePlus":{"type":"number","nullable":true},"time100":{"type":"number","nullable":true},"timeAll":{"type":"number","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","gameId","game","sourceId","timeMain","timePlus","time100","timeAll","createdAt","updatedAt"]},"AchievementDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"expGainAmount":{"type":"number"},"category":{"type":"number","enum":[0,1,2,3]}},"required":["id","name","description","expGainAmount","category"]},"PaginatedAchievementsResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AchievementDto"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"ObtainedAchievement":{"type":"object","properties":{"id":{"type":"number"},"achievementId":{"type":"string","description":"Achievement id specified in entries for achievements.data.ts"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"isFeatured":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","achievementId","profile","profileUserId","isFeatured","createdAt","updatedAt"]},"UpdateFeaturedObtainedAchievementDto":{"type":"object","properties":{"isFeatured":{"type":"boolean"}},"required":["isFeatured"]},"UserLevel":{"type":"object","properties":{"userId":{"type":"string","description":"Should be the same as the profile's UserId"},"profile":{"$ref":"#/components/schemas/Profile"},"currentLevel":{"type":"number"},"currentLevelExp":{"type":"number","description":"XP in the current user-level"},"levelUpExpCost":{"type":"number","description":"Threshold XP to hit the next user-level"},"expMultiplier":{"type":"number","description":"The multiplier to apply to all exp gains"}},"required":["userId","profile","currentLevel","currentLevelExp","levelUpExpCost","expMultiplier"]},"CreateUpdateCollectionEntryDto":{"type":"object","properties":{"finishedAt":{"type":"date-time"},"collectionIds":{"type":"array","items":{"type":"string"}},"gameId":{"type":"number"},"platformIds":{"type":"array","items":{"type":"number"}},"isFavorite":{"type":"boolean","default":false}},"required":["collectionIds","gameId","platformIds","isFavorite"]},"CreateFavoriteStatusCollectionEntryDto":{"type":"object","properties":{"isFavorite":{"type":"boolean","default":false}},"required":["isFavorite"]},"CollectionEntriesPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEntry"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FindCollectionEntriesDto":{"type":"object","properties":{"offset":{"type":"number","default":0},"limit":{"type":"number","default":20},"orderBy":{"type":"object"}}},"ActivitiesFeedPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Activity"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FollowStatusDto":{"type":"object","properties":{"isFollowing":{"type":"boolean"}},"required":["isFollowing"]},"FollowInfoRequestDto":{"type":"object","properties":{"criteria":{"type":"string","enum":["followers","following"]},"targetUserId":{"type":"string","minLength":36},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20},"orderBy":{"type":"object"}},"required":["criteria","targetUserId"]},"FollowInfoResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"string"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FollowRegisterDto":{"type":"object","properties":{"followedUserId":{"type":"string","minLength":36}},"required":["followedUserId"]},"FollowRemoveDto":{"type":"object","properties":{"followedUserId":{"type":"string","minLength":36}},"required":["followedUserId"]},"ImporterPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GameExternalGame"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"ImporterStatusUpdateRequestDto":{"type":"object","properties":{"status":{"type":"string"},"externalGameId":{"type":"number"}},"required":["status","externalGameId"]},"FindAvailableConnectionsResponseDto":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["steam"]},"isImporterViable":{"type":"boolean"},"iconName":{"type":"string"}},"required":["name","type","isImporterViable","iconName"]},"UserConnection":{"type":"object","properties":{"id":{"type":"number"},"type":{"type":"string","enum":["steam"]},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"sourceUserId":{"type":"string"},"sourceUsername":{"type":"string"},"isImporterViable":{"type":"boolean","description":"If this connection can be used by the 'importer' system."},"isImporterEnabled":{"type":"boolean"}},"required":["id","type","profile","profileUserId","sourceUserId","sourceUsername","isImporterViable","isImporterEnabled"]},"ConnectionCreateDto":{"type":"object","properties":{"type":{"type":"string","enum":["steam"]},"userIdentifier":{"type":"string","description":"A string representing a username, user id or profile URL for the target connection
\ne.g. a Steam's profile URL","minLength":1},"isImporterEnabled":{"type":"boolean","default":false}},"required":["type","userIdentifier","isImporterEnabled"]}}}} \ No newline at end of file +{"openapi":"3.0.0","paths":{"/v1/auth/logout":{"get":{"operationId":"AuthController_logout","parameters":[],"responses":{"200":{"description":""}},"tags":["auth"]}},"/v1/libraries":{"get":{"operationId":"LibrariesController_findOwn","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Library"}}}}},"tags":["libraries"]}},"/v1/libraries/{id}":{"get":{"operationId":"LibrariesController_findOneByIdWithPermissions","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Library"}}}}},"tags":["libraries"]}},"/v1/collections/{id}":{"get":{"operationId":"CollectionsController_findOneByIdWithPermissions","summary":"","description":"Returns a collection which the user has access to\n\n(Either its own collection or a public one)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collection"}}}}},"tags":["collections"]},"patch":{"operationId":"CollectionsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCollectionDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["collections"]},"delete":{"operationId":"CollectionsController_delete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["collections"]}},"/v1/collections/library/{userId}":{"get":{"operationId":"CollectionsController_findAllByUserIdWithPermissions","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}}}}}},"tags":["collections"]}},"/v1/collections":{"post":{"operationId":"CollectionsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCollectionDto"}}}},"responses":{"201":{"description":""}},"tags":["collections"]}},"/v1/reviews":{"post":{"operationId":"ReviewsController_createOrUpdate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateReviewDto"}}}},"responses":{"201":{"description":""}},"tags":["reviews"]},"get":{"operationId":"ReviewsController_findOneByUserIdAndGameId","parameters":[{"name":"id","required":true,"in":"query","schema":{"type":"string"}},{"name":"gameId","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}}},"tags":["reviews"]}},"/v1/reviews/all":{"post":{"operationId":"ReviewsController_findAllById","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindAllReviewsByIdDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Review"}}}}}},"tags":["reviews"]}},"/v1/reviews/score":{"get":{"operationId":"ReviewsController_getScoreForGameId","parameters":[{"name":"gameId","required":true,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewScoreResponseDto"}}}}},"tags":["reviews"]}},"/v1/reviews/profile/{userId}":{"get":{"operationId":"ReviewsController_findAllByUserId","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindReviewPaginatedDto"}}}}},"tags":["reviews"]}},"/v1/reviews/game/{id}":{"get":{"operationId":"ReviewsController_findAllByGameId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindReviewPaginatedDto"}}}}},"tags":["reviews"]}},"/v1/reviews/{id}":{"get":{"operationId":"ReviewsController_findOneById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}}},"tags":["reviews"]},"delete":{"operationId":"ReviewsController_delete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["reviews"]}},"/v1/profile":{"patch":{"operationId":"ProfileController_update","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/UpdateProfileDto"}}}},"responses":{"200":{"description":""}},"tags":["profile"]},"get":{"operationId":"ProfileController_findOwn","summary":"","description":"Used to access own profile","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}}},"tags":["profile"]}},"/v1/profile/{id}":{"get":{"operationId":"ProfileController_findOneById","summary":"","description":"Used to access other users' profiles","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}}},"tags":["profile"]}},"/v1/activities":{"get":{"operationId":"ActivitiesRepositoryController_findLatest","parameters":[{"name":"userId","required":false,"in":"query","schema":{"minLength":36,"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivitiesPaginatedResponseDto"}}}}},"tags":["activities"]}},"/v1/statistics/queue/like":{"post":{"operationId":"StatisticsQueueController_addLike","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsActionDto"}}}},"responses":{"201":{"description":""}},"tags":["statistics-queue"]},"delete":{"operationId":"StatisticsQueueController_removeLike","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsActionDto"}}}},"responses":{"200":{"description":""}},"tags":["statistics-queue"]}},"/v1/statistics/queue/view":{"post":{"operationId":"StatisticsQueueController_addView","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsActionDto"}}}},"responses":{"201":{"description":""}},"tags":["statistics-queue"]}},"/v1/statistics":{"post":{"operationId":"StatisticsController_findOneBySourceIdAndType","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindOneStatisticsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["statistics"]}},"/v1/statistics/trending/games":{"post":{"operationId":"StatisticsController_findTrendingGames","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindStatisticsTrendingGamesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameStatisticsPaginatedResponseDto"}}}}},"tags":["statistics"]}},"/v1/statistics/trending/reviews":{"post":{"operationId":"StatisticsController_findTrendingReviews","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindStatisticsTrendingReviewsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewStatisticsPaginatedResponseDto"}}}}},"tags":["statistics"]}},"/v1/statistics/trending/activities":{"post":{"operationId":"StatisticsController_findTrendingActivities","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindStatisticsTrendingActivitiesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewStatisticsPaginatedResponseDto"}}}}},"tags":["statistics"]}},"/v1/statistics/status":{"get":{"operationId":"StatisticsController_getStatus","parameters":[{"name":"statisticsId","required":true,"in":"query","schema":{"type":"number"}},{"name":"sourceType","required":true,"in":"query","schema":{"enum":["game","review","activity","review_comment"],"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatisticsStatus"}}}}},"tags":["statistics"]}},"/v1/notifications":{"get":{"operationId":"NotificationsController_findAllAndAggregate","parameters":[{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedNotificationAggregationDto"}}}}},"tags":["notifications"]}},"/v1/notifications/{id}/view":{"put":{"operationId":"NotificationsController_updateViewedStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationViewUpdateDto"}}}},"responses":{"200":{"description":""}},"tags":["notifications"]}},"/v1/game/repository/resource":{"get":{"operationId":"GameRepositoryController_getResource","parameters":[{"name":"resourceName","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["game-repository"]}},"/v1/game/repository/{id}/platforms/icon":{"get":{"operationId":"GameRepositoryController_getIconNamesForPlatformAbbreviations","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["game-repository"]}},"/v1/game/repository/{id}/external-stores":{"get":{"operationId":"GameRepositoryController_getExternalStoresForGameId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GameExternalStoreDto"}}}}}},"tags":["game-repository"]}},"/v1/game/repository/{id}":{"post":{"operationId":"GameRepositoryController_findOneById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameRepositoryFindOneDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Game"}}}}},"tags":["game-repository"]}},"/v1/game/repository":{"post":{"operationId":"GameRepositoryController_findAllByIds","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GameRepositoryFindAllDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}}}}},"tags":["game-repository"]}},"/v1/sync/hltb/{gameId}":{"get":{"operationId":"HltbController_findPlaytimeForGameId","parameters":[{"name":"gameId","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GamePlaytime"}}}}},"tags":["sync-hltb"]}},"/v1/achievements":{"get":{"operationId":"AchievementsController_getAchievements","parameters":[{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedAchievementsResponseDto"}}}}},"tags":["achievements"]}},"/v1/achievements/obtained/{id}":{"get":{"operationId":"AchievementsController_getObtainedAchievement","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"targetUserId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObtainedAchievement"}}}}},"tags":["achievements"]}},"/v1/achievements/obtained":{"get":{"operationId":"AchievementsController_getAllObtainedAchievements","parameters":[{"name":"targetUserId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ObtainedAchievement"}}}}}},"tags":["achievements"]}},"/v1/achievements/obtained/{id}/featured":{"put":{"operationId":"AchievementsController_updateFeaturedObtainedAchievement","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFeaturedObtainedAchievementDto"}}}},"responses":{"200":{"description":""}},"tags":["achievements"]}},"/v1/level/{userId}":{"get":{"operationId":"LevelController_findOne","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserLevel"}}}}},"tags":["level"]}},"/v1/collections-entries":{"post":{"operationId":"CollectionsEntriesController_createOrUpdate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUpdateCollectionEntryDto"}}}},"responses":{"201":{"description":""}},"tags":["collections-entries"]}},"/v1/collections-entries/{id}":{"get":{"operationId":"CollectionsEntriesController_findEntryById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntry"}}}}},"tags":["collections-entries"]},"delete":{"operationId":"CollectionsEntriesController_deleteOwnEntry","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"tags":["collections-entries"]}},"/v1/collections-entries/game/{id}":{"get":{"operationId":"CollectionsEntriesController_findOwnEntryByGameId","summary":"","description":"Returns a specific collection entry based on game ID","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntry"}}}},"400":{"description":"Invalid query"}},"tags":["collections-entries"]}},"/v1/collections-entries/{id}/platforms/icons":{"get":{"operationId":"CollectionsEntriesController_getIconsForOwnedPlatforms","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["collections-entries"]}},"/v1/collections-entries/game/{id}/favorite":{"post":{"operationId":"CollectionsEntriesController_changeFavoriteStatus","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFavoriteStatusCollectionEntryDto"}}}},"responses":{"204":{"description":""}},"tags":["collections-entries"]}},"/v1/collections-entries/library/{id}":{"get":{"operationId":"CollectionsEntriesController_findAllByLibraryId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntriesPaginatedResponseDto"}}}}},"tags":["collections-entries"]}},"/v1/collections-entries/library/{id}/favorites":{"get":{"operationId":"CollectionsEntriesController_findFavoritesByLibraryId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntriesPaginatedResponseDto"}}}}},"tags":["collections-entries"]}},"/v1/collections-entries/collection/{id}":{"post":{"operationId":"CollectionsEntriesController_findAllByCollectionId","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindCollectionEntriesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionEntriesPaginatedResponseDto"}}}}},"tags":["collections-entries"]}},"/v1/health":{"get":{"operationId":"HealthController_health","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["health"]}},"/v1/activities/feed":{"get":{"operationId":"ActivitiesFeedController_buildActivitiesFeed","parameters":[{"name":"criteria","required":true,"in":"query","schema":{"enum":["following","all"],"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivitiesFeedPaginatedResponseDto"}}}}},"tags":["activities-feed"]}},"/v1/follow/status":{"get":{"operationId":"FollowController_getFollowerStatus","parameters":[{"name":"followerUserId","required":true,"in":"query","schema":{"type":"string"}},{"name":"followedUserId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowStatusDto"}}}}},"tags":["follow"]}},"/v1/follow/{id}":{"get":{"operationId":"FollowController_getUserFollowById","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserFollow"}}}}},"tags":["follow"]}},"/v1/follow/info":{"post":{"operationId":"FollowController_getFollowInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowInfoRequestDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowInfoResponseDto"}}}}},"tags":["follow"]}},"/v1/follow":{"post":{"operationId":"FollowController_registerFollow","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowRegisterDto"}}}},"responses":{"201":{"description":""}},"tags":["follow"]},"delete":{"operationId":"FollowController_removeFollow","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FollowRemoveDto"}}}},"responses":{"200":{"description":""}},"tags":["follow"]}},"/v1/importer/{source}":{"get":{"operationId":"ImporterController_findUnprocessedEntries","parameters":[{"name":"source","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"default":20,"type":"number"}},{"name":"offset","required":false,"in":"query","schema":{"default":0,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImporterPaginatedResponseDto"}}}}},"tags":["importer"]}},"/v1/importer/status":{"post":{"operationId":"ImporterController_changeStatus","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImporterStatusUpdateRequestDto"}}}},"responses":{"201":{"description":""}},"tags":["importer"]}},"/v1/connections":{"get":{"operationId":"ConnectionsController_findAvailableConnections","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FindAvailableConnectionsResponseDto"}}}}}},"tags":["connections"]},"post":{"operationId":"ConnectionsController_createOrUpdate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionCreateDto"}}}},"responses":{"201":{"description":""}},"tags":["connections"]}},"/v1/connections/own":{"get":{"operationId":"ConnectionsController_findOwn","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserConnection"}}}}}},"tags":["connections"]}},"/v1/connections/own/{type}":{"get":{"operationId":"ConnectionsController_findOwnByType","parameters":[{"name":"type","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserConnection"}}}}},"tags":["connections"]}},"/v1/connections/{id}":{"delete":{"operationId":"ConnectionsController_delete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["connections"]}},"/v1/comment":{"post":{"operationId":"CommentController_findAll","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindAllCommentsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindCommentsPaginatedResponseDto"}}}}},"tags":["comment"]}},"/v1/comment/{sourceType}/{id}":{"get":{"operationId":"CommentController_findOneById","parameters":[{"name":"sourceType","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewComment"}}}}},"tags":["comment"]}},"/v1/comment/create":{"post":{"operationId":"CommentController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommentDto"}}}},"responses":{"201":{"description":""}},"tags":["comment"]}},"/v1/comment/{id}":{"patch":{"operationId":"CommentController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCommentDto"}}}},"responses":{"204":{"description":""}},"tags":["comment"]},"delete":{"operationId":"CommentController_delete","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCommentDto"}}}},"responses":{"200":{"description":""}},"tags":["comment"]}}},"info":{"title":"GameNode API","description":"API docs for the videogame catalog system GameNode.

Built with love by the GameNode team.","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"schemas":{"Library":{"type":"object","properties":{"userId":{"type":"string","description":"@description The primary key of the library entity.\nAlso used to share the library with other users.\n\nSame as SuperTokens' userId."},"collections":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["userId","collections","createdAt","updatedAt"]},"GameCover":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["game","id"]},"GameCollection":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"slug":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"checksum":{"type":"string"},"url":{"type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}},"required":["id","name","slug","createdAt","updatedAt","checksum","url","games"]},"GameAlternativeName":{"type":"object","properties":{"id":{"type":"number"},"comment":{"type":"string"},"name":{"type":"string"},"checksum":{"type":"string"},"game":{"$ref":"#/components/schemas/Game"}},"required":["id","game"]},"GameArtwork":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["game","id"]},"GameScreenshot":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["game","id"]},"GameLocalization":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["game","id","createdAt","updatedAt"]},"GameMode":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["game","id","createdAt","updatedAt"]},"GameGenre":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["games","id","createdAt","updatedAt"]},"GameTheme":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","createdAt","updatedAt"]},"GamePlayerPerspective":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["games","id","createdAt","updatedAt"]},"GameEngineLogo":{"type":"object","properties":{"engine":{"$ref":"#/components/schemas/GameEngine"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["engine","id"]},"GameCompanyLogo":{"type":"object","properties":{"company":{"$ref":"#/components/schemas/GameCompany"},"id":{"type":"number"},"alphaChannel":{"type":"boolean"},"animated":{"type":"boolean"},"height":{"type":"number"},"imageId":{"type":"string"},"url":{"type":"string"},"width":{"type":"number"},"checksum":{"type":"string"}},"required":["company","id"]},"GameCompany":{"type":"object","properties":{"id":{"type":"number"},"changeDate":{"format":"date-time","type":"string"},"changeDateCategory":{"type":"string"},"changedCompany":{"$ref":"#/components/schemas/GameCompany"},"checksum":{"type":"string"},"country":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"logo":{"$ref":"#/components/schemas/GameCompanyLogo"},"name":{"type":"string"},"parent":{"$ref":"#/components/schemas/GameCompany"},"slug":{"type":"string"},"startDate":{"format":"date-time","type":"string"},"startDateCategory":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"url":{"type":"string"}},"required":["id","createdAt","name","slug","updatedAt"]},"GamePlatform":{"type":"object","properties":{"id":{"type":"number"},"abbreviation":{"type":"string"},"alternative_name":{"type":"string"},"category":{"type":"number","enum":[1,2,3,4,5,6]},"checksum":{"type":"string"},"generation":{"type":"number"},"name":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"collectionEntries":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEntry"}}},"required":["id","abbreviation","alternative_name","category","checksum","generation","name","createdAt","updatedAt","games","collectionEntries"]},"GameEngine":{"type":"object","properties":{"logo":{"$ref":"#/components/schemas/GameEngineLogo"},"companies":{"type":"array","items":{"$ref":"#/components/schemas/GameCompany"}},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/GamePlatform"}},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["logo","companies","platforms","games","id","createdAt","updatedAt"]},"GameKeyword":{"type":"object","properties":{"game":{"$ref":"#/components/schemas/Game"},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["game","id","createdAt","updatedAt"]},"GameFranchise":{"type":"object","properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"id":{"type":"number"},"checksum":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["games","id","createdAt","updatedAt"]},"GameExternalGame":{"type":"object","properties":{"id":{"type":"number"},"uid":{"type":"string","description":"Corresponds to the game id on the target source (see GameExternalGameCategory).\nIt's called uid, not uuid."},"category":{"type":"number","enum":[1,5,10,11,13,14,15,20,22,23,26,28,29,30,31,32,36,37,54,55]},"media":{"type":"number","enum":[1,2]},"checksum":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"year":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"}},"required":["id","uid","createdAt","updatedAt","game","gameId"]},"GameInvolvedCompany":{"type":"object","properties":{"id":{"type":"number"},"checksum":{"type":"string"},"company":{"$ref":"#/components/schemas/GameCompany"},"companyId":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"developer":{"type":"boolean"},"porting":{"type":"boolean"},"publisher":{"type":"boolean"},"supporting":{"type":"boolean"},"updatedAt":{"format":"date-time","type":"string"},"games":{"type":"array","items":{"$ref":"#/components/schemas/Game"}}},"required":["id","company","companyId","createdAt","developer","porting","publisher","supporting","updatedAt","games"]},"Game":{"type":"object","properties":{"id":{"type":"number","description":"Should be mapped to the IGDB ID of the game."},"name":{"type":"string"},"slug":{"type":"string"},"aggregatedRating":{"type":"number"},"aggregatedRatingCount":{"type":"number"},"category":{"enum":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"type":"number"},"status":{"enum":[0,2,3,4,5,6,7,8],"type":"number"},"summary":{"type":"string"},"storyline":{"type":"string"},"checksum":{"type":"string"},"url":{"type":"string"},"firstReleaseDate":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"dlcs":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"dlcOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expansions":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expansionOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expandedGames":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"expandedGameOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"similarGames":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"similarGameOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remakes":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remakeOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remasters":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"remasterOf":{"type":"array","items":{"$ref":"#/components/schemas/Game"}},"cover":{"$ref":"#/components/schemas/GameCover"},"collection":{"$ref":"#/components/schemas/GameCollection"},"alternativeNames":{"type":"array","items":{"$ref":"#/components/schemas/GameAlternativeName"}},"artworks":{"type":"array","items":{"$ref":"#/components/schemas/GameArtwork"}},"screenshots":{"type":"array","items":{"$ref":"#/components/schemas/GameScreenshot"}},"gameLocalizations":{"type":"array","items":{"$ref":"#/components/schemas/GameLocalization"}},"gameModes":{"type":"array","items":{"$ref":"#/components/schemas/GameMode"}},"genres":{"type":"array","items":{"$ref":"#/components/schemas/GameGenre"}},"themes":{"type":"array","items":{"$ref":"#/components/schemas/GameTheme"}},"playerPerspectives":{"type":"array","items":{"$ref":"#/components/schemas/GamePlayerPerspective"}},"gameEngines":{"type":"array","items":{"$ref":"#/components/schemas/GameEngine"}},"keywords":{"type":"array","items":{"$ref":"#/components/schemas/GameKeyword"}},"franchises":{"type":"array","items":{"$ref":"#/components/schemas/GameFranchise"}},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/GamePlatform"}},"externalGames":{"type":"array","items":{"$ref":"#/components/schemas/GameExternalGame"}},"involvedCompanies":{"type":"array","items":{"$ref":"#/components/schemas/GameInvolvedCompany"}},"source":{"type":"string","description":"Oh dear maintainer, please forgive me for using transient fields.","default":"MYSQL","enum":["MYSQL","MANTICORE"]}},"required":["id","name","slug","category","status","summary","storyline","checksum","url","firstReleaseDate","createdAt","updatedAt","involvedCompanies","source"]},"ProfileAvatar":{"type":"object","properties":{"id":{"type":"number"},"mimetype":{"type":"string"},"extension":{"type":"string"},"size":{"type":"number"},"filename":{"type":"string"},"encoding":{"type":"string"},"profile":{"$ref":"#/components/schemas/Profile"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","mimetype","extension","size","filename","encoding","profile","createdAt","updatedAt"]},"UserFollow":{"type":"object","properties":{"id":{"type":"number"},"follower":{"$ref":"#/components/schemas/Profile"},"followerUserId":{"type":"string"},"followed":{"$ref":"#/components/schemas/Profile"},"followedUserId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","follower","followerUserId","followed","followedUserId","createdAt","updatedAt"]},"Profile":{"type":"object","properties":{"userId":{"type":"string","description":"Shareable string ID\n\nSame as SuperTokens' userId."},"username":{"type":"string"},"bio":{"type":"string"},"avatar":{"$ref":"#/components/schemas/ProfileAvatar"},"followers":{"type":"array","items":{"$ref":"#/components/schemas/UserFollow"}},"following":{"type":"array","items":{"$ref":"#/components/schemas/UserFollow"}},"usernameLastUpdatedAt":{"format":"date-time","type":"string","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["userId","username","bio","avatar","followers","following","usernameLastUpdatedAt","createdAt","updatedAt"]},"Review":{"type":"object","properties":{"id":{"type":"string"},"content":{"type":"string","nullable":true},"rating":{"type":"number"},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"collectionEntry":{"$ref":"#/components/schemas/CollectionEntry"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","content","rating","game","gameId","profile","profileUserId","collectionEntry","createdAt","updatedAt"]},"CollectionEntry":{"type":"object","properties":{"id":{"type":"string"},"collections":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"},"ownedPlatforms":{"description":"The platforms on which the user owns the game.","type":"array","items":{"$ref":"#/components/schemas/GamePlatform"}},"review":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Review"}]},"isFavorite":{"type":"boolean"},"finishedAt":{"format":"date-time","type":"string","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","collections","game","gameId","ownedPlatforms","review","isFavorite","finishedAt","createdAt","updatedAt"]},"Collection":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"isPublic":{"type":"boolean"},"library":{"$ref":"#/components/schemas/Library"},"libraryUserId":{"type":"string"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEntry"}},"isFeatured":{"type":"boolean"},"isFinished":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","name","description","isPublic","library","libraryUserId","entries","isFeatured","isFinished","createdAt","updatedAt"]},"CreateCollectionDto":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"isPublic":{"type":"boolean"},"isFeatured":{"type":"boolean"},"isFinished":{"type":"boolean"}},"required":["name","isPublic","isFeatured","isFinished"]},"UpdateCollectionDto":{"type":"object","properties":{}},"CreateReviewDto":{"type":"object","properties":{"gameId":{"type":"number"},"content":{"type":"string","minLength":20},"rating":{"type":"number","minimum":0,"maximum":5}},"required":["gameId","content","rating"]},"FindAllReviewsByIdDto":{"type":"object","properties":{"reviewsIds":{"type":"array","items":{"type":"string"}}},"required":["reviewsIds"]},"ReviewScoreDistribution":{"type":"object","properties":{"1":{"type":"number"},"2":{"type":"number"},"3":{"type":"number"},"4":{"type":"number"},"5":{"type":"number"},"total":{"type":"number","description":"Total number of reviews"}},"required":["1","2","3","4","5","total"]},"ReviewScoreResponseDto":{"type":"object","properties":{"median":{"type":"number"},"distribution":{"$ref":"#/components/schemas/ReviewScoreDistribution"}},"required":["median","distribution"]},"PaginationInfo":{"type":"object","properties":{"totalItems":{"type":"number","description":"Total number of items available for the current query"},"totalPages":{"type":"number","description":"Total number of pages available for the current query"},"hasNextPage":{"type":"boolean","description":"If this query allows for a next page"}},"required":["totalItems","totalPages","hasNextPage"]},"FindReviewPaginatedDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"UpdateProfileDto":{"type":"object","properties":{"username":{"type":"string","minLength":4,"maxLength":20},"avatar":{"type":"object"},"bio":{"type":"string","minLength":1,"maxLength":240}}},"Activity":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["REVIEW","FOLLOW","COLLECTION_ENTRY"]},"profile":{"description":"The associated profile with this Activity (e.g. user who performed an action)","allOf":[{"$ref":"#/components/schemas/Profile"}]},"profileUserId":{"type":"string"},"collectionEntry":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/CollectionEntry"}]},"collectionEntryId":{"type":"string","nullable":true},"collection":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Collection"}]},"collectionId":{"type":"string","nullable":true},"review":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Review"}]},"reviewId":{"type":"string","nullable":true},"userFollow":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/UserFollow"}]},"userFollowId":{"type":"number","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","type","profile","profileUserId","collectionEntry","collectionEntryId","collection","collectionId","review","reviewId","userFollow","userFollowId","createdAt","updatedAt"]},"ActivitiesPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Activity"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"StatisticsActionDto":{"type":"object","properties":{"sourceId":{"oneOf":[{"type":"string"},{"type":"number"}]},"targetUserId":{"type":"string","minLength":36},"sourceType":{"enum":["game","review","activity","review_comment"],"type":"string"}},"required":["sourceId","sourceType"]},"FindOneStatisticsDto":{"type":"object","properties":{"sourceId":{"oneOf":[{"type":"string"},{"type":"number"}]},"sourceType":{"type":"string","enum":["game","review","activity","review_comment"]}},"required":["sourceId","sourceType"]},"GameRepositoryFilterDto":{"type":"object","properties":{"ids":{"description":"If this is supplied, filtering will be done only for entities specified here.
\nUseful to filter data received from entities which hold game ids (like GameStatistics, Reviews, etc.)","type":"array","items":{"type":"number"}},"status":{"type":"number","enum":[0,2,3,4,5,6,7,8]},"category":{"type":"number","enum":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]},"themes":{"type":"array","items":{"type":"number"}},"gameModes":{"type":"array","items":{"type":"number"}},"platforms":{"type":"array","items":{"type":"number"}},"genres":{"type":"array","items":{"type":"number"}},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}}},"FindStatisticsTrendingGamesDto":{"type":"object","properties":{"criteria":{"$ref":"#/components/schemas/GameRepositoryFilterDto"},"period":{"type":"string","enum":["day","week","month","quarter","half_year","year","all"]},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}},"required":["period"]},"ActivityStatistics":{"type":"object","properties":{"views":{"type":"array","items":{"$ref":"#/components/schemas/UserView"}},"likes":{"type":"array","items":{"$ref":"#/components/schemas/UserLike"}},"activity":{"$ref":"#/components/schemas/Activity"},"activityId":{"type":"string"},"id":{"type":"number"},"viewsCount":{"type":"number"},"likesCount":{"type":"number"}},"required":["views","likes","activity","activityId","id","viewsCount","likesCount"]},"ReviewComment":{"type":"object","properties":{"review":{"$ref":"#/components/schemas/Review"},"reviewId":{"type":"string"},"id":{"type":"string"},"content":{"type":"string","description":"HTML content of the user's comment."},"profile":{"description":"Author of this comment","allOf":[{"$ref":"#/components/schemas/Profile"}]},"profileUserId":{"type":"string","description":"User id of the author of this comment"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["review","reviewId","id","content","profile","profileUserId","createdAt","updatedAt"]},"CommentStatistics":{"type":"object","properties":{"views":{"type":"array","items":{"$ref":"#/components/schemas/UserView"}},"likes":{"type":"array","items":{"$ref":"#/components/schemas/UserLike"}},"reviewComment":{"$ref":"#/components/schemas/ReviewComment"},"reviewCommentId":{"type":"string"},"id":{"type":"number"},"viewsCount":{"type":"number"},"likesCount":{"type":"number"}},"required":["views","likes","reviewComment","reviewCommentId","id","viewsCount","likesCount"]},"UserLike":{"type":"object","properties":{"id":{"type":"number"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"gameStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/GameStatistics"}]},"gameStatisticsId":{"type":"number","nullable":true},"reviewStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ReviewStatistics"}]},"reviewStatisticsId":{"type":"number","nullable":true},"activityStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ActivityStatistics"}]},"activityStatisticsId":{"type":"number","nullable":true},"commentStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/CommentStatistics"}]},"commentStatisticsId":{"type":"number","nullable":true}},"required":["id","profile","profileUserId","createdAt","updatedAt","gameStatistics","gameStatisticsId","reviewStatistics","reviewStatisticsId","activityStatistics","activityStatisticsId","commentStatistics","commentStatisticsId"]},"ReviewStatistics":{"type":"object","properties":{"views":{"type":"array","items":{"$ref":"#/components/schemas/UserView"}},"likes":{"type":"array","items":{"$ref":"#/components/schemas/UserLike"}},"review":{"$ref":"#/components/schemas/Review"},"reviewId":{"type":"string"},"id":{"type":"number"},"viewsCount":{"type":"number"},"likesCount":{"type":"number"}},"required":["views","likes","review","reviewId","id","viewsCount","likesCount"]},"UserView":{"type":"object","properties":{"id":{"type":"number"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"gameStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/GameStatistics"}]},"reviewStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ReviewStatistics"}]},"activityStatistics":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/ActivityStatistics"}]},"commentStatistics":{"type":"object"}},"required":["id","profileUserId","createdAt","updatedAt","gameStatistics","reviewStatistics","activityStatistics","commentStatistics"]},"GameStatistics":{"type":"object","properties":{"views":{"type":"array","items":{"$ref":"#/components/schemas/UserView"}},"likes":{"type":"array","items":{"$ref":"#/components/schemas/UserLike"}},"game":{"$ref":"#/components/schemas/Game"},"gameId":{"type":"number"},"id":{"type":"number"},"viewsCount":{"type":"number"},"likesCount":{"type":"number"}},"required":["views","likes","game","gameId","id","viewsCount","likesCount"]},"GameStatisticsPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GameStatistics"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FindStatisticsTrendingReviewsDto":{"type":"object","properties":{"reviewId":{"type":"string","description":"Usually, this property should not be used unless a specific review needs to be retrieved, and it's easier to just\ncall the statistics controller."},"gameId":{"type":"number"},"userId":{"type":"string","minLength":36},"period":{"type":"string","enum":["day","week","month","quarter","half_year","year","all"]},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}},"required":["period"]},"ReviewStatisticsPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ReviewStatistics"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FindStatisticsTrendingActivitiesDto":{"type":"object","properties":{"activityId":{"type":"string","description":"Usually, this property should not be used unless a specific activity needs to be retrieved, and it's easier to just\ncall the statistics controller.","minLength":36},"userId":{"type":"string","minLength":36},"activityType":{"type":"string","enum":["REVIEW","FOLLOW","COLLECTION_ENTRY"]},"period":{"type":"string","enum":["day","week","month","quarter","half_year","year","all"]},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20}},"required":["period"]},"StatisticsStatus":{"type":"object","properties":{"isLiked":{"type":"boolean"},"isViewed":{"type":"boolean"}},"required":["isLiked","isViewed"]},"Notification":{"type":"object","properties":{"id":{"type":"number"},"sourceType":{"type":"string","enum":["game","review","activity","profile"]},"category":{"type":"string","description":"What this notification's about. E.g.: a new like, a new follower, a game launch, etc.","enum":["follow","like","comment","launch"]},"review":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Review"}]},"reviewId":{"type":"string","nullable":true},"game":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Game"}]},"gameId":{"type":"number","nullable":true},"activity":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Activity"}]},"activityId":{"type":"string","nullable":true},"profile":{"nullable":true,"description":"User responsible for generating this notification (e.g. user that liked a review).","allOf":[{"$ref":"#/components/schemas/Profile"}]},"profileUserId":{"type":"string","nullable":true,"description":"User responsible for generating this notification (e.g. user that liked a review).\nWhen null/undefined, the notification was generated by the 'system'."},"isViewed":{"type":"boolean"},"targetProfile":{"nullable":true,"description":"User which is the target for this notification.
\nIf this is empty (null/undefined), the notification is targeted at all users.
\nNot to be confused with the 'profile' property.","allOf":[{"$ref":"#/components/schemas/Profile"}]},"targetProfileUserId":{"type":"string","nullable":true,"description":"User which is the target for this notification.
\nIf this is empty (null/undefined), the notification is targeted at all users.
\nNot to be confused with the 'profile' property."},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","sourceType","category","review","reviewId","game","gameId","activity","activityId","profile","profileUserId","isViewed","targetProfile","targetProfileUserId","createdAt","updatedAt"]},"NotificationAggregateDto":{"type":"object","properties":{"sourceId":{"oneOf":[{"type":"string"},{"type":"number"}]},"category":{"type":"string","enum":["follow","like","comment","launch"]},"sourceType":{"type":"string","enum":["game","review","activity","profile"]},"notifications":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}}},"required":["sourceId","category","sourceType","notifications"]},"PaginatedNotificationAggregationDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NotificationAggregateDto"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"NotificationViewUpdateDto":{"type":"object","properties":{"isViewed":{"type":"boolean"}},"required":["isViewed"]},"GameExternalStoreDto":{"type":"object","properties":{"icon":{"type":"string","nullable":true,"description":"Icon representing said store/service."},"storeName":{"type":"string","nullable":true},"id":{"type":"number"},"uid":{"type":"string","description":"Corresponds to the game id on the target source (see GameExternalGameCategory).\nIt's called uid, not uuid."},"category":{"type":"number","enum":[1,5,10,11,13,14,15,20,22,23,26,28,29,30,31,32,36,37,54,55]},"media":{"type":"number","enum":[1,2]},"checksum":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"year":{"type":"number"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"},"gameId":{"type":"number"}},"required":["icon","storeName","id","uid","createdAt","updatedAt","gameId"]},"GameRepositoryFindOneDto":{"type":"object","properties":{"relations":{"type":"object"}}},"GameRepositoryFindAllDto":{"type":"object","properties":{"gameIds":{"type":"array","items":{"type":"number"}},"relations":{"type":"object"}},"required":["gameIds"]},"GamePlaytime":{"type":"object","properties":{"id":{"type":"number"},"gameId":{"type":"number"},"game":{"$ref":"#/components/schemas/Game"},"sourceId":{"type":"number"},"timeMain":{"type":"number","nullable":true},"timePlus":{"type":"number","nullable":true},"time100":{"type":"number","nullable":true},"timeAll":{"type":"number","nullable":true},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","gameId","game","sourceId","timeMain","timePlus","time100","timeAll","createdAt","updatedAt"]},"AchievementDto":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"expGainAmount":{"type":"number"},"category":{"type":"number","enum":[0,1,2,3]}},"required":["id","name","description","expGainAmount","category"]},"PaginatedAchievementsResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AchievementDto"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"ObtainedAchievement":{"type":"object","properties":{"id":{"type":"number"},"achievementId":{"type":"string","description":"Achievement id specified in entries for achievements.data.ts"},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"isFeatured":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"required":["id","achievementId","profile","profileUserId","isFeatured","createdAt","updatedAt"]},"UpdateFeaturedObtainedAchievementDto":{"type":"object","properties":{"isFeatured":{"type":"boolean"}},"required":["isFeatured"]},"UserLevel":{"type":"object","properties":{"userId":{"type":"string","description":"Should be the same as the profile's UserId"},"profile":{"$ref":"#/components/schemas/Profile"},"currentLevel":{"type":"number"},"currentLevelExp":{"type":"number","description":"XP in the current user-level"},"levelUpExpCost":{"type":"number","description":"Threshold XP to hit the next user-level"},"expMultiplier":{"type":"number","description":"The multiplier to apply to all exp gains"}},"required":["userId","profile","currentLevel","currentLevelExp","levelUpExpCost","expMultiplier"]},"CreateUpdateCollectionEntryDto":{"type":"object","properties":{"finishedAt":{"type":"date-time"},"collectionIds":{"type":"array","items":{"type":"string"}},"gameId":{"type":"number"},"platformIds":{"type":"array","items":{"type":"number"}},"isFavorite":{"type":"boolean","default":false}},"required":["collectionIds","gameId","platformIds","isFavorite"]},"CreateFavoriteStatusCollectionEntryDto":{"type":"object","properties":{"isFavorite":{"type":"boolean","default":false}},"required":["isFavorite"]},"CollectionEntriesPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CollectionEntry"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FindCollectionEntriesDto":{"type":"object","properties":{"offset":{"type":"number","default":0},"limit":{"type":"number","default":20},"orderBy":{"type":"object"}}},"ActivitiesFeedPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Activity"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FollowStatusDto":{"type":"object","properties":{"isFollowing":{"type":"boolean"}},"required":["isFollowing"]},"FollowInfoRequestDto":{"type":"object","properties":{"criteria":{"type":"string","enum":["followers","following"]},"targetUserId":{"type":"string","minLength":36},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20},"orderBy":{"type":"object"}},"required":["criteria","targetUserId"]},"FollowInfoResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"string"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"FollowRegisterDto":{"type":"object","properties":{"followedUserId":{"type":"string","minLength":36}},"required":["followedUserId"]},"FollowRemoveDto":{"type":"object","properties":{"followedUserId":{"type":"string","minLength":36}},"required":["followedUserId"]},"ImporterPaginatedResponseDto":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GameExternalGame"}},"pagination":{"$ref":"#/components/schemas/PaginationInfo"}},"required":["data","pagination"]},"ImporterStatusUpdateRequestDto":{"type":"object","properties":{"status":{"type":"string"},"externalGameId":{"type":"number"}},"required":["status","externalGameId"]},"FindAvailableConnectionsResponseDto":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["steam"]},"isImporterViable":{"type":"boolean"},"iconName":{"type":"string"}},"required":["name","type","isImporterViable","iconName"]},"UserConnection":{"type":"object","properties":{"id":{"type":"number"},"type":{"type":"string","enum":["steam"]},"profile":{"$ref":"#/components/schemas/Profile"},"profileUserId":{"type":"string"},"sourceUserId":{"type":"string"},"sourceUsername":{"type":"string"},"isImporterViable":{"type":"boolean","description":"If this connection can be used by the 'importer' system."},"isImporterEnabled":{"type":"boolean"}},"required":["id","type","profile","profileUserId","sourceUserId","sourceUsername","isImporterViable","isImporterEnabled"]},"ConnectionCreateDto":{"type":"object","properties":{"type":{"type":"string","enum":["steam"]},"userIdentifier":{"type":"string","description":"A string representing a username, user id or profile URL for the target connection
\ne.g. a Steam's profile URL","minLength":1},"isImporterEnabled":{"type":"boolean","default":false}},"required":["type","userIdentifier","isImporterEnabled"]},"FindAllCommentsDto":{"type":"object","properties":{"sourceId":{"type":"string","minLength":36},"sourceType":{"type":"string","enum":["review"]},"offset":{"type":"number","default":0},"limit":{"type":"number","default":20},"orderBy":{"type":"object"}},"required":["sourceId","sourceType"]},"FindCommentsPaginatedResponseDto":{"type":"object","properties":{"data":{"default":[],"type":"array","items":{"$ref":"#/components/schemas/ReviewComment"}},"pagination":{"default":{},"allOf":[{"$ref":"#/components/schemas/PaginationInfo"}]}},"required":["data","pagination"]},"CreateCommentDto":{"type":"object","properties":{"sourceId":{"type":"string","description":"UUID of the target entity. Comments can only be attributed to\nUUID based entities.","minLength":36},"sourceType":{"type":"string","enum":["review"]},"content":{"type":"string","minLength":1}},"required":["sourceId","sourceType","content"]},"UpdateCommentDto":{"type":"object","properties":{"sourceType":{"type":"string","enum":["review"]},"content":{"type":"string","minLength":1}},"required":["sourceType","content"]},"DeleteCommentDto":{"type":"object","properties":{"sourceType":{"type":"string","enum":["review"]}},"required":["sourceType"]}}}} \ No newline at end of file