From 11d74d846b071e68a3938f0b19ea9098c7ea9ac0 Mon Sep 17 00:00:00 2001 From: CookieDasora Date: Tue, 25 Jul 2023 10:57:23 -0300 Subject: [PATCH] Created like-post and follow-user routes, changed file names --- README.md | 4 +- .../migration.sql | 15 +++++ .../migration.sql | 22 ++++++++ .../migration.sql | 32 +++++++++++ prisma/schema.prisma | 26 +++++++-- src/controllers/posts-router.ts | 8 +-- .../posts/{post-create.ts => create.ts} | 0 .../posts/{post-delete.ts => delete.ts} | 0 .../posts/{post-info.ts => get-info.ts} | 0 .../posts/{post-update.ts => update.ts} | 0 src/controllers/users-router.ts | 16 ++++-- .../users/{user-auth.ts => auth.ts} | 0 .../users/{user-delete.ts => delete.ts} | 0 src/controllers/users/follow-user.ts | 18 ++++++ .../users/{user-info.ts => get-info.ts} | 0 src/controllers/users/like-post.ts | 18 ++++++ .../users/{user-signup.ts => signup.ts} | 0 .../users/{user-update.ts => update.ts} | 0 ...er-upload-picture.ts => upload-picture.ts} | 0 src/services/index.ts | 26 +++++---- .../posts/{post-create.ts => create.ts} | 0 .../posts/{post-delete.ts => delete.ts} | 0 .../posts/{post-info.ts => get-info.ts} | 3 +- src/services/posts/get-likes.ts | 0 .../posts/{post-update.ts => update.ts} | 0 src/services/users/{user-auth.ts => auth.ts} | 2 +- .../users/{user-delete.ts => delete.ts} | 0 src/services/users/follow-user.ts | 55 +++++++++++++++++++ .../users/{user-info.ts => get-info.ts} | 5 ++ src/services/users/like-post.ts | 55 +++++++++++++++++++ .../users/{user-signup.ts => signup.ts} | 0 .../users/{user-update.ts => update.ts} | 3 + ...er-upload-picture.ts => upload-picture.ts} | 13 +---- 33 files changed, 280 insertions(+), 41 deletions(-) create mode 100644 prisma/migrations/20230724180557_created_like_field/migration.sql create mode 100644 prisma/migrations/20230724182328_created_user_follow_and_following_relations/migration.sql create mode 100644 prisma/migrations/20230725123252_alter_user_follow_and_following_relations/migration.sql rename src/controllers/posts/{post-create.ts => create.ts} (100%) rename src/controllers/posts/{post-delete.ts => delete.ts} (100%) rename src/controllers/posts/{post-info.ts => get-info.ts} (100%) rename src/controllers/posts/{post-update.ts => update.ts} (100%) rename src/controllers/users/{user-auth.ts => auth.ts} (100%) rename src/controllers/users/{user-delete.ts => delete.ts} (100%) create mode 100644 src/controllers/users/follow-user.ts rename src/controllers/users/{user-info.ts => get-info.ts} (100%) create mode 100644 src/controllers/users/like-post.ts rename src/controllers/users/{user-signup.ts => signup.ts} (100%) rename src/controllers/users/{user-update.ts => update.ts} (100%) rename src/controllers/users/{user-upload-picture.ts => upload-picture.ts} (100%) rename src/services/posts/{post-create.ts => create.ts} (100%) rename src/services/posts/{post-delete.ts => delete.ts} (100%) rename src/services/posts/{post-info.ts => get-info.ts} (95%) create mode 100644 src/services/posts/get-likes.ts rename src/services/posts/{post-update.ts => update.ts} (100%) rename src/services/users/{user-auth.ts => auth.ts} (93%) rename src/services/users/{user-delete.ts => delete.ts} (100%) create mode 100644 src/services/users/follow-user.ts rename src/services/users/{user-info.ts => get-info.ts} (87%) create mode 100644 src/services/users/like-post.ts rename src/services/users/{user-signup.ts => signup.ts} (100%) rename src/services/users/{user-update.ts => update.ts} (87%) rename src/services/users/{user-upload-picture.ts => upload-picture.ts} (67%) diff --git a/README.md b/README.md index 4a7024e..1a052cb 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ An open-source social media. - Able to choose a profile picture✅ - Probably gonna use LocalStack to mock Amazon S3✅ - Image compression ✅ -- Following/unfollowing features -- Like posts +- Following/unfollowing features ✅ +- Like posts ✅ - Probably pinned posts - Authentication ✅ - Add more verification (like, if the password is too short) ✅ diff --git a/prisma/migrations/20230724180557_created_like_field/migration.sql b/prisma/migrations/20230724180557_created_like_field/migration.sql new file mode 100644 index 0000000..f7acbf5 --- /dev/null +++ b/prisma/migrations/20230724180557_created_like_field/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "Like" ( + "id" TEXT NOT NULL, + "postId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Like_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Like" ADD CONSTRAINT "Like_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Like" ADD CONSTRAINT "Like_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20230724182328_created_user_follow_and_following_relations/migration.sql b/prisma/migrations/20230724182328_created_user_follow_and_following_relations/migration.sql new file mode 100644 index 0000000..2ba3397 --- /dev/null +++ b/prisma/migrations/20230724182328_created_user_follow_and_following_relations/migration.sql @@ -0,0 +1,22 @@ +-- CreateTable +CREATE TABLE "Follower" ( + "id" TEXT NOT NULL, + "followerId" TEXT NOT NULL, + + CONSTRAINT "Follower_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Following" ( + "id" TEXT NOT NULL, + "followingId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Following_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Follower" ADD CONSTRAINT "Follower_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Following" ADD CONSTRAINT "Following_followingId_fkey" FOREIGN KEY ("followingId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20230725123252_alter_user_follow_and_following_relations/migration.sql b/prisma/migrations/20230725123252_alter_user_follow_and_following_relations/migration.sql new file mode 100644 index 0000000..cfaff0a --- /dev/null +++ b/prisma/migrations/20230725123252_alter_user_follow_and_following_relations/migration.sql @@ -0,0 +1,32 @@ +/* + Warnings: + + - You are about to drop the `Follower` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `Following` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Follower" DROP CONSTRAINT "Follower_followerId_fkey"; + +-- DropForeignKey +ALTER TABLE "Following" DROP CONSTRAINT "Following_followingId_fkey"; + +-- DropTable +DROP TABLE "Follower"; + +-- DropTable +DROP TABLE "Following"; + +-- CreateTable +CREATE TABLE "Follows" ( + "followerId" TEXT NOT NULL, + "followingId" TEXT NOT NULL, + + CONSTRAINT "Follows_pkey" PRIMARY KEY ("followerId","followingId") +); + +-- AddForeignKey +ALTER TABLE "Follows" ADD CONSTRAINT "Follows_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Follows" ADD CONSTRAINT "Follows_followingId_fkey" FOREIGN KEY ("followingId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 801d4e1..390d2bb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - generator client { provider = "prisma-client-js" } @@ -18,6 +15,9 @@ model User { password String posts Post[] profileImage String? + likedPosts Like[] + followers Follows[] @relation("following") + following Follows[] @relation("follower") createdAt DateTime @default(now()) } @@ -26,6 +26,24 @@ model Post { content String authorId String author User @relation(fields: [authorId], references: [id]) + likes Like[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt -} \ No newline at end of file +} + +model Like { + id String @id @default(uuid()) + postId String + post Post @relation(fields: [postId], references: [id]) + userId String + user User @relation(fields: [userId], references: [id]) + createdAt DateTime @default(now()) +} +model Follows { + follower User @relation("follower", fields: [followerId], references: [id]) + followerId String + following User @relation("following", fields: [followingId], references: [id]) + followingId String + + @@id([followerId, followingId]) +} diff --git a/src/controllers/posts-router.ts b/src/controllers/posts-router.ts index 7c8ff96..f19b878 100644 --- a/src/controllers/posts-router.ts +++ b/src/controllers/posts-router.ts @@ -3,10 +3,10 @@ import { Router } from 'express' // Controllers -import postCreateController from './posts/post-create' -import postDeleteController from './posts/post-delete' -import postInfoController from './posts/post-info' -import postUpdateController from './posts/post-update' +import postCreateController from './posts/create' +import postDeleteController from './posts/delete' +import postInfoController from './posts/get-info' +import postUpdateController from './posts/update' // Middlewares import ensureAuthenticated from '../middlewares/ensure-authenticated' diff --git a/src/controllers/posts/post-create.ts b/src/controllers/posts/create.ts similarity index 100% rename from src/controllers/posts/post-create.ts rename to src/controllers/posts/create.ts diff --git a/src/controllers/posts/post-delete.ts b/src/controllers/posts/delete.ts similarity index 100% rename from src/controllers/posts/post-delete.ts rename to src/controllers/posts/delete.ts diff --git a/src/controllers/posts/post-info.ts b/src/controllers/posts/get-info.ts similarity index 100% rename from src/controllers/posts/post-info.ts rename to src/controllers/posts/get-info.ts diff --git a/src/controllers/posts/post-update.ts b/src/controllers/posts/update.ts similarity index 100% rename from src/controllers/posts/post-update.ts rename to src/controllers/posts/update.ts diff --git a/src/controllers/users-router.ts b/src/controllers/users-router.ts index d9d4f35..94b0169 100644 --- a/src/controllers/users-router.ts +++ b/src/controllers/users-router.ts @@ -3,12 +3,14 @@ import { Router } from 'express' // Controllers -import userAuthController from './users/user-auth' -import userDeleteController from './users/user-delete' -import userInfoController from './users/user-info' -import userSignupController from './users/user-signup' -import userUpdateController from './users/user-update' -import userUploadPictureController from './users/user-upload-picture' +import userAuthController from './users/auth' +import userDeleteController from './users/delete' +import userFollowController from './users/follow-user' +import userInfoController from './users/get-info' +import userLikePostController from './users/like-post' +import userSignupController from './users/signup' +import userUpdateController from './users/update' +import userUploadPictureController from './users/upload-picture' // Middlewares import ensureAuthenticated from '../middlewares/ensure-authenticated' @@ -23,5 +25,7 @@ usersRouter.get('/info', userInfoController) usersRouter.post('/signup', userSignupController) usersRouter.put('/update', ensureAuthenticated, userUpdateController) usersRouter.put('/profile-picture/upload', ensureAuthenticated, uploadFile, userUploadPictureController) +usersRouter.post('/like-post', ensureAuthenticated, userLikePostController) +usersRouter.post('/follow-user', ensureAuthenticated, userFollowController) export default usersRouter diff --git a/src/controllers/users/user-auth.ts b/src/controllers/users/auth.ts similarity index 100% rename from src/controllers/users/user-auth.ts rename to src/controllers/users/auth.ts diff --git a/src/controllers/users/user-delete.ts b/src/controllers/users/delete.ts similarity index 100% rename from src/controllers/users/user-delete.ts rename to src/controllers/users/delete.ts diff --git a/src/controllers/users/follow-user.ts b/src/controllers/users/follow-user.ts new file mode 100644 index 0000000..16516ac --- /dev/null +++ b/src/controllers/users/follow-user.ts @@ -0,0 +1,18 @@ +import { user } from '../../services' +import type { Request, Response } from 'express' +import { badRequest } from '../../lib/http-errors' + +async function userFollowController (req: Request, res: Response): Promise { + const userId = req.user?.id ?? '' + const { userToFollow } = req.body + + const result = await user.follow(userId, userToFollow) + + if (result instanceof Error) { + return badRequest(res, result.message) + } + + res.json(result) +} + +export default userFollowController diff --git a/src/controllers/users/user-info.ts b/src/controllers/users/get-info.ts similarity index 100% rename from src/controllers/users/user-info.ts rename to src/controllers/users/get-info.ts diff --git a/src/controllers/users/like-post.ts b/src/controllers/users/like-post.ts new file mode 100644 index 0000000..e2df36c --- /dev/null +++ b/src/controllers/users/like-post.ts @@ -0,0 +1,18 @@ +import { user } from '../../services' +import type { Request, Response } from 'express' +import { badRequest } from '../../lib/http-errors' + +async function userLikePostController (req: Request, res: Response): Promise { + const userId = req.user?.id ?? '' + const { postId } = req.body + + const result = await user.likePost(postId, userId) + + if (result instanceof Error) { + return badRequest(res, result.message) + } + + res.json(result) +} + +export default userLikePostController diff --git a/src/controllers/users/user-signup.ts b/src/controllers/users/signup.ts similarity index 100% rename from src/controllers/users/user-signup.ts rename to src/controllers/users/signup.ts diff --git a/src/controllers/users/user-update.ts b/src/controllers/users/update.ts similarity index 100% rename from src/controllers/users/user-update.ts rename to src/controllers/users/update.ts diff --git a/src/controllers/users/user-upload-picture.ts b/src/controllers/users/upload-picture.ts similarity index 100% rename from src/controllers/users/user-upload-picture.ts rename to src/controllers/users/upload-picture.ts diff --git a/src/services/index.ts b/src/services/index.ts index a2b1203..6be1287 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,14 +1,16 @@ -import userAuthService from './users/user-auth' -import userDeleteService from './users/user-delete' -import userInfoService from './users/user-info' -import userSignupService from './users/user-signup' -import userUpdateService from './users/user-update' -import userUploadPictureService from './users/user-upload-picture' +import userAuthService from './users/auth' +import userDeleteService from './users/delete' +import userFollowService from './users/follow-user' +import userInfoService from './users/get-info' +import userLikePostService from './users/like-post' +import userSignupService from './users/signup' +import userUpdateService from './users/update' +import userUploadPictureService from './users/upload-picture' -import postCreateService from './posts/post-create' -import postDeleteService from './posts/post-delete' -import postInfoService from './posts/post-info' -import postUpdateService from './posts/post-update' +import postCreateService from './posts/create' +import postDeleteService from './posts/delete' +import postInfoService from './posts/get-info' +import postUpdateService from './posts/update' // User services const user = { @@ -17,7 +19,9 @@ const user = { info: userInfoService, signup: userSignupService, update: userUpdateService, - uploadPicture: userUploadPictureService + uploadPicture: userUploadPictureService, + likePost: userLikePostService, + follow: userFollowService } // Post services diff --git a/src/services/posts/post-create.ts b/src/services/posts/create.ts similarity index 100% rename from src/services/posts/post-create.ts rename to src/services/posts/create.ts diff --git a/src/services/posts/post-delete.ts b/src/services/posts/delete.ts similarity index 100% rename from src/services/posts/post-delete.ts rename to src/services/posts/delete.ts diff --git a/src/services/posts/post-info.ts b/src/services/posts/get-info.ts similarity index 95% rename from src/services/posts/post-info.ts rename to src/services/posts/get-info.ts index 96334e2..8e26366 100644 --- a/src/services/posts/post-info.ts +++ b/src/services/posts/get-info.ts @@ -15,7 +15,8 @@ async function postInfoService (id: string): Promise { displayName: true, username: true } - } + }, + likes: true } }) diff --git a/src/services/posts/get-likes.ts b/src/services/posts/get-likes.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/services/posts/post-update.ts b/src/services/posts/update.ts similarity index 100% rename from src/services/posts/post-update.ts rename to src/services/posts/update.ts diff --git a/src/services/users/user-auth.ts b/src/services/users/auth.ts similarity index 93% rename from src/services/users/user-auth.ts rename to src/services/users/auth.ts index 61510e8..d2807d1 100644 --- a/src/services/users/user-auth.ts +++ b/src/services/users/auth.ts @@ -10,7 +10,7 @@ async function userAuthService (email: string, password: string): Promise { + if (userId === undefined || followingUsername === undefined) { + return new Error('Missing fields') + } + + const user = await prisma.user.findFirst({ + where: { + username: followingUsername + } + }) + + if (user === null) { + return new Error('User not found') + } + + const userToFollow = await prisma.user.findFirst({ + where: { + id: userId + } + }) + + if (userToFollow === null) { + return new Error('User to follow not found') + } + + const alreadyFollow = await prisma.follows.findFirst({ + where: { + followerId: user.id, + followingId: userToFollow.id + } + }) + + if (alreadyFollow !== null) { + await prisma.follows.deleteMany({ + where: { + followerId: user.id, + followingId: userToFollow.id + } + }) + return {} + } + + const follow = await prisma.follows.create({ + data: { + followerId: user.id, + followingId: userToFollow.id + } + }) + + return follow +} + +export default userFollowService diff --git a/src/services/users/user-info.ts b/src/services/users/get-info.ts similarity index 87% rename from src/services/users/user-info.ts rename to src/services/users/get-info.ts index 6943274..eece93f 100644 --- a/src/services/users/user-info.ts +++ b/src/services/users/get-info.ts @@ -17,6 +17,11 @@ async function userInfoService (username: string): Promise { createdAt: true, updatedAt: true } + }, + likedPosts: { + select: { + postId: true + } } } }) diff --git a/src/services/users/like-post.ts b/src/services/users/like-post.ts new file mode 100644 index 0000000..e8116ac --- /dev/null +++ b/src/services/users/like-post.ts @@ -0,0 +1,55 @@ +import prisma from '../../clients/prisma-client' + +async function userLikePostService (postId: string, userId: string): Promise { + if (postId === undefined || userId === undefined) { + return new Error('Missing fields') + } + + const post = await prisma.post.findFirst({ + where: { + id: postId + } + }) + + if (post === null) { + return new Error('Post not found') + } + + const user = await prisma.user.findFirst({ + where: { + id: userId + } + }) + + if (user === null) { + return new Error('User not found') + } + + const alreadyLiked = await prisma.like.findFirst({ + where: { + postId: post.id, + userId: user.id + } + }) + + if (alreadyLiked !== null) { + await prisma.like.deleteMany({ + where: { + postId: post.id, + userId: user.id + } + }) + return {} + } + + const like = await prisma.like.create({ + data: { + postId: post.id, + userId: user.id + } + }) + + return like +} + +export default userLikePostService diff --git a/src/services/users/user-signup.ts b/src/services/users/signup.ts similarity index 100% rename from src/services/users/user-signup.ts rename to src/services/users/signup.ts diff --git a/src/services/users/user-update.ts b/src/services/users/update.ts similarity index 87% rename from src/services/users/user-update.ts rename to src/services/users/update.ts index 2ca88b5..fceccc3 100644 --- a/src/services/users/user-update.ts +++ b/src/services/users/update.ts @@ -8,6 +8,9 @@ async function userUpdateService ({ id, email, displayName, username }: userPayl return new Error('User not found') } + // Check if the provided email or username is different from the current user's data + // if they are different, additional queries are made to check if the new email or username is already in use. + if (email !== undefined && email.trim() !== user.email) { const existingUser = await prisma.user.findFirst({ where: { email } }) if ((existingUser != null) && existingUser.email !== user.email) { diff --git a/src/services/users/user-upload-picture.ts b/src/services/users/upload-picture.ts similarity index 67% rename from src/services/users/user-upload-picture.ts rename to src/services/users/upload-picture.ts index 2f3c2cd..abcfb21 100644 --- a/src/services/users/user-upload-picture.ts +++ b/src/services/users/upload-picture.ts @@ -15,18 +15,7 @@ async function userUploadPictureService (authorId: string, url: string): Promise profileImage: url }, select: { - profileImage: true, - displayName: true, - username: true, - createdAt: true, - posts: { - select: { - id: true, - content: true, - createdAt: true, - updatedAt: true - } - } + profileImage: true } })