From 66dd9ddfcda7f37bf5ad921e844f6307e09ccee4 Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 02:28:36 +0000 Subject: [PATCH 01/24] feat(post.dto): add optional imageUrl to CreatePostDTO --- server/src/modules/post/post.dto.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/modules/post/post.dto.ts b/server/src/modules/post/post.dto.ts index b544dbb..9e83671 100644 --- a/server/src/modules/post/post.dto.ts +++ b/server/src/modules/post/post.dto.ts @@ -17,6 +17,10 @@ export class CreatePostDTO { @IsString() forumId: string; + @ApiProperty({ description: 'The image URL of the post', required: false }) + @IsString() + imageUrl?: string; + // @ApiProperty({ description: 'The ID of the user creating the post' }) // @IsNotEmpty() // @IsString() From 6c26c185568f02ff47b390b08c8995a1d9ad736c Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 02:29:09 +0000 Subject: [PATCH 02/24] feat(post.service): handle imageUrl in post creation --- server/src/modules/post/post.service.ts | 34 +++++++++++++++++++------ 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/server/src/modules/post/post.service.ts b/server/src/modules/post/post.service.ts index 9358fde..e051058 100644 --- a/server/src/modules/post/post.service.ts +++ b/server/src/modules/post/post.service.ts @@ -12,7 +12,7 @@ export class PostService { constructor(private prisma: PrismaService) {} async create(userId: string, postData: CreatePostDTO): Promise { - const { title, content, forumId } = postData; + const { title, content, forumId, imageUrl } = postData; const slug = slugify(title, { lower: true, strict: true }).substring( 0, 100, @@ -26,19 +26,37 @@ export class PostService { throw new Error('Forum not found'); } - return this.prisma.post.create({ + // Create the post + const post = await this.prisma.post.create({ data: { title, content, slug, - user: { - connect: { id: userId }, - }, - forum: { - connect: { id: forumId }, - }, + userId, + forumId, }, }); + + // Create the attachment + if (imageUrl) { + const attachment = await this.prisma.attachment.create({ + data: { + name: 'Image for post ' + title, + type: 'image', + url: imageUrl, + }, + }); + + // Connect the post and the attachment + await this.prisma.postAttachment.create({ + data: { + postId: post.id, + attachmentId: attachment.id, + }, + }); + } + + return post; } async searchPosts(searchTerm: string): Promise { From 47f012306da2f9696a82fb3ca62362cda095ce4f Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 03:00:33 +0000 Subject: [PATCH 03/24] fix(side-bar): update image src to prevent caching --- client/src/components/core/side-bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/core/side-bar.tsx b/client/src/components/core/side-bar.tsx index fdaef8a..9a18a5f 100644 --- a/client/src/components/core/side-bar.tsx +++ b/client/src/components/core/side-bar.tsx @@ -74,7 +74,7 @@ export default function SideBar() {
{forum.name} Date: Thu, 4 Apr 2024 03:00:56 +0000 Subject: [PATCH 04/24] feat(forum.dto): add optional logo and banner to CreateForumDTO --- server/src/modules/forum/forum.dto.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/modules/forum/forum.dto.ts b/server/src/modules/forum/forum.dto.ts index 64138ef..4ecebb1 100644 --- a/server/src/modules/forum/forum.dto.ts +++ b/server/src/modules/forum/forum.dto.ts @@ -17,6 +17,14 @@ export class CreateForumDTO { @IsString() description: string; + @ApiProperty({ description: 'The logo URL of the forum', required: false }) + @IsString() + logo?: string; + + @ApiProperty({ description: 'The banner URL of the forum', required: false }) + @IsString() + banner?: string; + // @ApiProperty({ description: 'The ID of the user who owns the forum' }) // @IsString() // ownerUserId: string; From 6d0481c21252dd92f0a35c883591277e53078189 Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 03:01:14 +0000 Subject: [PATCH 05/24] feat(forum.service): handle logo and banner in forum creation --- server/src/modules/forum/forum.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/modules/forum/forum.service.ts b/server/src/modules/forum/forum.service.ts index 6751f1c..0475a4c 100644 --- a/server/src/modules/forum/forum.service.ts +++ b/server/src/modules/forum/forum.service.ts @@ -263,7 +263,7 @@ export class ForumService { } async create(userId: string, forumData: CreateForumDTO): Promise { - const { name, description, slug } = forumData; + const { name, description, slug, logo, banner } = forumData; const existingForum = await this.prisma.forum.findUnique({ where: { slug }, }); @@ -276,6 +276,8 @@ export class ForumService { name, description, slug, + logo, + banner, owner: { connect: { id: userId }, }, From f1baf53f8e89214aa41c9a5b3cf176f5dcf12a3c Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 04:01:30 +0000 Subject: [PATCH 06/24] feat(post.service): modify findAll to fetch all posts by default --- server/src/modules/post/post.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/modules/post/post.controller.ts b/server/src/modules/post/post.controller.ts index 35c721d..553c491 100644 --- a/server/src/modules/post/post.controller.ts +++ b/server/src/modules/post/post.controller.ts @@ -53,7 +53,7 @@ export class PostController { @Query('take') take: string, ): Promise { const pageNumber = parseInt(page, 10) || 0; - const takeNumber = parseInt(take, 10) || 10; + const takeNumber = parseInt(take, 10) || 99999; //for now testing return this.postService.findAll({ page: pageNumber, take: takeNumber }); } From 295e959cb28b0b1f1132ec9c396f1e9ed179f02b Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 04:09:10 +0000 Subject: [PATCH 07/24] feat(post.controller): add order query parameter to getAllPosts method --- server/src/modules/post/post.controller.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/server/src/modules/post/post.controller.ts b/server/src/modules/post/post.controller.ts index 553c491..4d8d957 100644 --- a/server/src/modules/post/post.controller.ts +++ b/server/src/modules/post/post.controller.ts @@ -46,16 +46,29 @@ export class PostController { type: Number, description: 'Number of posts per page (default is 10)', }) + @ApiQuery({ + name: 'order', + required: false, + type: String, + description: + "Order of posts ('asc' for oldest first, 'desc' for latest first, default is 'desc')", + }) @ApiResponse({ status: 200, description: 'Return all posts with pagination' }) @ApiOperation({ summary: 'Get all posts with pagination' }) async getAllPosts( @Query('page') page: string, @Query('take') take: string, + @Query('order') order: 'asc' | 'desc', ): Promise { const pageNumber = parseInt(page, 10) || 0; - const takeNumber = parseInt(take, 10) || 99999; //for now testing + const takeNumber = parseInt(take, 10) || 10; + const orderDirection = order || 'desc'; - return this.postService.findAll({ page: pageNumber, take: takeNumber }); + return this.postService.findAll({ + page: pageNumber, + take: takeNumber, + orderBy: { createdAt: orderDirection }, + }); } @Get('post/:id') From 8ad9162d07f607d8c60c21079c2e1ca14256b1bc Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 05:35:09 +0000 Subject: [PATCH 08/24] feat(post): add CommentInput component to post page --- client/src/app/(post)/post/[slug]/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/app/(post)/post/[slug]/page.tsx b/client/src/app/(post)/post/[slug]/page.tsx index 92843ec..480efb7 100644 --- a/client/src/app/(post)/post/[slug]/page.tsx +++ b/client/src/app/(post)/post/[slug]/page.tsx @@ -9,7 +9,7 @@ import { Button } from "~/components/ui/button"; import { FaPlus } from "react-icons/fa6"; import FullPost from "~/components/posts/fullpost"; import Comment from "~/components/comment/comment"; - +import CommentInput from "~/components/comment/commentInput"; function Page({ params }: { params: { slug: string } }) { const { getPost, getComments } = useFetcher(); const { data: postData, error: postError } = useSWR(`getPost/${params.slug}`, getPost); @@ -21,6 +21,7 @@ function Page({ params }: { params: { slug: string } }) { {postData && (
+
)} From 5fde9cb576d912df54f4636f2f9987a87ab2386a Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 05:35:42 +0000 Subject: [PATCH 09/24] feat(fetcher): add getUser function and update postComment function --- client/src/hooks/fetcher.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/client/src/hooks/fetcher.tsx b/client/src/hooks/fetcher.tsx index e0d7e12..da070e2 100644 --- a/client/src/hooks/fetcher.tsx +++ b/client/src/hooks/fetcher.tsx @@ -74,7 +74,7 @@ export const useFetcher = (filter = 'hot') => { } } - const postComment = async (key: string, content: string, parentId: string) => { + const postComment = async (key: string, content: string, parentId: string | null) => { const postId = key.replace('postComment/', ''); try { const response = await axios.post('/comments/comment', { @@ -105,6 +105,18 @@ export const useFetcher = (filter = 'hot') => { } } + const getUser = async () => { + try { + const response = await axios.get('/users/me', { + withCredentials: true + }); + return response.data; + } catch (error) { + // @ts-ignore + throw new Error(error); + } + } + return { allPosts, allForums, @@ -113,6 +125,7 @@ export const useFetcher = (filter = 'hot') => { createPost, getPost, postComment, - getComments + getComments, + getUser, } } From 377ee043f08ff608e3a59f3d6ba0f3be29a889c5 Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 05:35:57 +0000 Subject: [PATCH 10/24] refactor(comment-service): update comment creation logic --- server/src/modules/comment/comment.service.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/server/src/modules/comment/comment.service.ts b/server/src/modules/comment/comment.service.ts index 4b728a0..8200a92 100644 --- a/server/src/modules/comment/comment.service.ts +++ b/server/src/modules/comment/comment.service.ts @@ -154,22 +154,24 @@ export class CommentService { }, ): Promise { const { content, postId, parentId } = CommentData; + console.log('CommentData', CommentData); try { - const comment = await this.prisma.comment.create({ - data: { - content, - user: { - connect: { id: userId }, - }, - post: { - connect: { id: postId }, - }, - parent: parentId - ? { - connect: { id: parentId }, - } - : null, + const data: any = { + content, + user: { + connect: { id: userId }, + }, + post: { + connect: { id: postId }, }, + }; + if (parentId) { + data.parent = { + connect: { id: parentId }, + }; + } + const comment = await this.prisma.comment.create({ + data, }); return comment; } catch (error) { From 890a6e47afd421d14553e20edc92542b89b9652c Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 05:36:25 +0000 Subject: [PATCH 11/24] feat(post-controller): add order query parameter to getCommentsByPostId function --- server/src/modules/post/post.controller.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server/src/modules/post/post.controller.ts b/server/src/modules/post/post.controller.ts index 4d8d957..30ae2f3 100644 --- a/server/src/modules/post/post.controller.ts +++ b/server/src/modules/post/post.controller.ts @@ -127,8 +127,17 @@ export class PostController { description: 'Return all comments of the post with the given ID', }) @ApiResponse({ status: 404, description: 'Post not found' }) - async getCommentsByPostId(@Param('id') id: string): Promise { - return this.postService.findCommentsByPostId(id); + @ApiQuery({ + name: 'order', + required: false, + type: String, + description: 'Order of the comments (default is "desc")', + }) + async getCommentsByPostId( + @Param('id') id: string, + @Query('order') order: 'asc' | 'desc', + ): Promise { + return this.postService.findCommentsByPostId(id, order || 'desc'); } // @Get('feed') From 5f1a8f04088f283e81d572979c7b52a09f2371d3 Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 05:37:48 +0000 Subject: [PATCH 12/24] feat(post-service): add order parameter to findCommentsByPostId function --- server/src/modules/post/post.service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/src/modules/post/post.service.ts b/server/src/modules/post/post.service.ts index e051058..3017d5d 100644 --- a/server/src/modules/post/post.service.ts +++ b/server/src/modules/post/post.service.ts @@ -217,11 +217,17 @@ export class PostService { }); } - async findCommentsByPostId(postId: string): Promise { + async findCommentsByPostId( + postId: string, + order: 'asc' | 'desc', + ): Promise { let comments = await this.prisma.comment.findMany({ where: { postId: postId, }, + orderBy: { + createdAt: order, + }, include: { user: { select: { From 0ac9d570af81cd63a6f6e19d00b3589658c34204 Mon Sep 17 00:00:00 2001 From: Mounssif BOUHLAOUI Date: Thu, 4 Apr 2024 05:38:01 +0000 Subject: [PATCH 13/24] feat(comment-input): add new CommentInput component --- .../src/components/comment/commentInput.tsx | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 client/src/components/comment/commentInput.tsx diff --git a/client/src/components/comment/commentInput.tsx b/client/src/components/comment/commentInput.tsx new file mode 100644 index 0000000..7addbfd --- /dev/null +++ b/client/src/components/comment/commentInput.tsx @@ -0,0 +1,56 @@ +import { useFetcher } from '~/hooks/fetcher'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useState, useEffect } from 'react'; + +interface CommentInputProps { + postId: string; +} +interface User { + username: string; + avatarUrl: string; + } +const CommentInput: React.FC = ({ postId }) => { + const [content, setContent] = useState(''); + const [user, setUser] = useState(null); + const { postComment, getUser } = useFetcher(); + + useEffect(() => { + const fetchUser = async () => { + const userData = await getUser(); + setUser(userData); + }; + + fetchUser(); + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + await postComment(`postComment/${postId}`, content, null); + setContent(''); + }; + + return ( +
+ {user && ( + <> + + logo + +
+