diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 17ed93a..09901be 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -17,6 +17,7 @@ model User { password String posts Post[] role Role @default(USER) + likes Like[] } enum Role { @@ -32,4 +33,14 @@ model Post { author User @relation(fields: [authorId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + likes Int @default(0) + likedBy Like[] +} + +model Like { + id Int @id @default(autoincrement()) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + postId Int + post Post @relation(fields: [postId], references: [id], onDelete: Cascade) } diff --git a/src/api/posts/index.ts b/src/api/posts/index.ts index 63eb08c..1538bd2 100644 --- a/src/api/posts/index.ts +++ b/src/api/posts/index.ts @@ -3,6 +3,8 @@ import getPosts from './getPosts'; import postPost from './newPost'; import putPost from './editPost'; import deletePost from './deletePost'; +import likePost from './likePost'; +import unlikePost from './unlikePost'; const posts = Router(); @@ -10,5 +12,7 @@ posts.get('/', getPosts); posts.post('/new', postPost); posts.put('/edit/:id', putPost); posts.delete('/delete/:id', deletePost); +posts.put('/like/:id', likePost); +posts.put('/unlike/:id', unlikePost); export default posts; diff --git a/src/api/posts/likePost.ts b/src/api/posts/likePost.ts new file mode 100644 index 0000000..5634d3d --- /dev/null +++ b/src/api/posts/likePost.ts @@ -0,0 +1,16 @@ +import { likePost } from '@/controller/PostController'; +import { Request, Response } from 'express'; + +export default async (req: Request, res: Response) => { + try { + const id = parseInt(req.params.id); + const userId = 1; // hardcoded for now, use userId from token + const likedPost = await likePost(id, userId); + if (likedPost instanceof Error) { + return res.status(403).send(likedPost.message); + } + return res.status(200).send({ message: 'Post liked' }); + } catch (error) { + return res.status(500).send(error); + } +}; diff --git a/src/api/posts/unlikePost.ts b/src/api/posts/unlikePost.ts new file mode 100644 index 0000000..791963b --- /dev/null +++ b/src/api/posts/unlikePost.ts @@ -0,0 +1,18 @@ +import { unlikePost } from '@/controller/PostController'; +import { Request, Response } from 'express'; + +export default async (req: Request, res: Response) => { + try { + const postId = parseInt(req.params.id); + const userId = 1; // hardcoded for now, use userId from token + const likedPost = await unlikePost(postId, userId); + if (likedPost instanceof Error) { + return res.status(403).send(likedPost.message); + } + return res.status(200).send({ message: 'Post unliked' }); + } catch (error) { + console.log(error); + + return res.status(500).send(error); + } +}; diff --git a/src/controller/PostController.ts b/src/controller/PostController.ts index e8c70a9..0d42537 100644 --- a/src/controller/PostController.ts +++ b/src/controller/PostController.ts @@ -1,4 +1,4 @@ -import { PrismaClient, Post as PrismaPost, User } from '@prisma/client'; +import { PrismaClient, Post as PrismaPost, Like } from '@prisma/client'; import { Post } from '@/models/PostModel'; const prisma = new PrismaClient(); @@ -15,6 +15,9 @@ const getPostById = async (id: number): Promise => { where: { id, }, + include: { + likedBy: true, + }, }); return post; }; @@ -23,6 +26,7 @@ const getAllPosts = async (): Promise => { const posts = prisma.post.findMany({ include: { author: true, + likedBy: true, }, }); return (await posts).map((post) => { @@ -90,4 +94,100 @@ const deletePost = async (id: number, userId: number): Promise => { + const post = await prisma.post.findUnique({ + where: { + id: postId, + }, + include: { + likedBy: true, + }, + }); + if (post === null) { + return new Error('Post not found'); + } + const user = await prisma.user.findUnique({ + where: { + id: userId, + }, + include: { + likes: true, + }, + }); + if (user === null) { + return new Error('User not found'); + } + const like = post.likedBy.find((likes) => likes.postId === postId && likes.userId === userId); + if (like === undefined) { + return new Error('Post not liked'); + } + + await prisma.post.update({ + where: { + id: postId, + }, + data: { + likes: { + decrement: 1, + }, + }, + }); + + return prisma.like.delete({ + where: { + id: like.id, + }, + }); +}; + +const likePost = async (id: number, userId: number): Promise => { + const post = await prisma.post.findUnique({ + where: { + id, + }, + include: { + likedBy: true, + }, + }); + if (post === null) { + return new Error('Post not found'); + } + // if (post.authorId === userId) { + // return new Error('User cannot like their own post'); + // } + if (post.likedBy.some((like) => like.userId === userId)) { + return new Error('Post already liked'); + } + const newLike = await prisma.like.create({ + data: { + post: { + connect: { + id, + }, + }, + user: { + connect: { + id: userId, + }, + }, + }, + }); + + return prisma.post.update({ + where: { + id, + }, + data: { + likedBy: { + connect: { + id: newLike.id, + }, + }, + likes: { + increment: 1, + }, + }, + }); +}; + +export { getAllPosts, createPost, editPost, getPostById, deletePost, likePost, unlikePost }; diff --git a/src/models/LikeModel.ts b/src/models/LikeModel.ts new file mode 100644 index 0000000..646c695 --- /dev/null +++ b/src/models/LikeModel.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; +import { User } from './UserModel'; +import { Post } from './PostModel'; + +interface Like { + id?: number | undefined; + userId: number; + user: User; + postId: number; + post: Post; +} + +const Like: z.ZodType = z.object({ + id: z.number().optional(), + userId: z.number(), + user: User, + postId: z.number(), + post: Post, +}); + +export { Like }; diff --git a/src/models/PostModel.ts b/src/models/PostModel.ts index 987b849..4397d65 100644 --- a/src/models/PostModel.ts +++ b/src/models/PostModel.ts @@ -1,10 +1,13 @@ import { z } from 'zod'; +import { Like } from './LikeModel'; interface Post { id?: number | undefined; content?: string | undefined; image?: string | undefined; authorId: number; + likes?: number | undefined; + likedBy?: Like[] | undefined; } const Post: z.ZodType = z.object({ @@ -12,6 +15,8 @@ const Post: z.ZodType = z.object({ content: z.string().optional(), image: z.string().optional(), authorId: z.number(), + likes: z.number().optional(), + likedBy: z.array(Like).optional(), }); export { Post }; diff --git a/src/models/UserModel.ts b/src/models/UserModel.ts index 5a3e4bb..3ee15fa 100644 --- a/src/models/UserModel.ts +++ b/src/models/UserModel.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; import { Post } from './PostModel'; +import { Like } from './LikeModel'; interface User { id?: number | undefined; @@ -9,6 +10,7 @@ interface User { lastName: string; role?: string | undefined; posts?: Post[] | undefined; + likes?: Like[] | undefined; } const User: z.ZodType = z.object({ @@ -19,6 +21,7 @@ const User: z.ZodType = z.object({ lastName: z.string(), role: z.string().optional(), posts: z.array(Post).optional(), + likes: z.array(Like).optional(), }); export { User };