diff --git a/lib/api/controllers/users/userId/deleteUserById.ts b/lib/api/controllers/users/userId/deleteUserById.ts new file mode 100644 index 0000000..21262b5 --- /dev/null +++ b/lib/api/controllers/users/userId/deleteUserById.ts @@ -0,0 +1,118 @@ +import { prisma } from "@/lib/api/db"; +import bcrypt from "bcrypt"; +import removeFolder from "@/lib/api/storage/removeFolder"; +import Stripe from "stripe"; + +type DeleteUserBody = { + password: string; + cancellation_details?: { + comment?: string; + feedback?: Stripe.SubscriptionCancelParams.CancellationDetails.Feedback; + }; +}; + +export default async function deleteUserById( + userId: number, + body: DeleteUserBody +) { + // First, we retrieve the user from the database + const user = await prisma.user.findUnique({ + where: { id: userId }, + }); + + if (!user) { + return { + response: "User not found.", + status: 404, + }; + } + + // Then, we check if the provided password matches the one stored in the database + const isPasswordValid = bcrypt.compareSync(body.password, user.password); + + if (!isPasswordValid) { + return { + response: "Invalid password.", + status: 401, // Unauthorized + }; + } + + // Delete the user and all related data within a transaction + await prisma.$transaction(async (prisma) => { + // Delete whitelisted users + await prisma.whitelistedUser.deleteMany({ + where: { userId }, + }); + + // Delete links + await prisma.link.deleteMany({ + where: { collection: { ownerId: userId } }, + }); + + // Delete tags + await prisma.tag.deleteMany({ + where: { ownerId: userId }, + }); + + // Delete collections + const collections = await prisma.collection.findMany({ + where: { ownerId: userId }, + }); + + for (const collection of collections) { + // Delete related users and collections relations + await prisma.usersAndCollections.deleteMany({ + where: { collectionId: collection.id }, + }); + + // Optionally delete archive folders associated with collections + removeFolder({ filePath: `archives/${collection.id}` }); + } + + // Delete collections after cleaning up related data + await prisma.collection.deleteMany({ + where: { ownerId: userId }, + }); + + // Optionally delete user's avatar + removeFolder({ filePath: `uploads/avatar/${userId}.jpg` }); + + // Finally, delete the user + await prisma.user.delete({ + where: { id: userId }, + }); + }); + + if (process.env.STRIPE_SECRET_KEY) { + const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { + apiVersion: "2022-11-15", + }); + + const listByEmail = await stripe.customers.list({ + email: user.email?.toLowerCase(), + expand: ["data.subscriptions"], + }); + + if (listByEmail.data[0].subscriptions?.data[0].id) { + const deleted = await stripe.subscriptions.cancel( + listByEmail.data[0].subscriptions?.data[0].id, + { + cancellation_details: { + comment: body.cancellation_details?.comment, + feedback: body.cancellation_details?.feedback, + }, + } + ); + + return { + response: deleted, + status: 200, + }; + } + } + + return { + response: "User account and all related data deleted successfully.", + status: 200, + }; +} diff --git a/lib/api/controllers/users/userId/getPublicUserById.ts b/lib/api/controllers/users/userId/getPublicUserById.ts index 0d00af0..d0c844e 100644 --- a/lib/api/controllers/users/userId/getPublicUserById.ts +++ b/lib/api/controllers/users/userId/getPublicUserById.ts @@ -1,6 +1,6 @@ import { prisma } from "@/lib/api/db"; -export default async function getUser( +export default async function getPublicUserById( targetId: number | string, isId: boolean, requestingUsername?: string diff --git a/lib/api/controllers/users/userId/getUserById.ts b/lib/api/controllers/users/userId/getUserById.ts index 2b29540..32bc3a6 100644 --- a/lib/api/controllers/users/userId/getUserById.ts +++ b/lib/api/controllers/users/userId/getUserById.ts @@ -1,6 +1,6 @@ import { prisma } from "@/lib/api/db"; -export default async function getUser(userId: number) { +export default async function getUserById(userId: number) { const user = await prisma.user.findUnique({ where: { id: userId, diff --git a/lib/api/controllers/users/userId/updateUserById.ts b/lib/api/controllers/users/userId/updateUserById.ts index c7ac3e8..6ceb764 100644 --- a/lib/api/controllers/users/userId/updateUserById.ts +++ b/lib/api/controllers/users/userId/updateUserById.ts @@ -9,7 +9,7 @@ import createFolder from "@/lib/api/storage/createFolder"; const emailEnabled = process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false; -export default async function updateUser( +export default async function updateUserById( sessionUser: { id: number; username: string; diff --git a/pages/api/v1/users/[id].ts b/pages/api/v1/users/[id].ts index 112ceff..20b3551 100644 --- a/pages/api/v1/users/[id].ts +++ b/pages/api/v1/users/[id].ts @@ -4,6 +4,7 @@ import { authOptions } from "@/pages/api/v1/auth/[...nextauth]"; import getUserById from "@/lib/api/controllers/users/userId/getUserById"; import getPublicUserById from "@/lib/api/controllers/users/userId/getPublicUserById"; import updateUserById from "@/lib/api/controllers/users/userId/updateUserById"; +import deleteUserById from "@/lib/api/controllers/users/userId/deleteUserById"; export default async function users(req: NextApiRequest, res: NextApiResponse) { const session = await getServerSession(req, res, authOptions); @@ -36,5 +37,12 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { } else if (req.method === "PUT") { const updated = await updateUserById(session.user, req.body); return res.status(updated.status).json({ response: updated.response }); + } else if ( + req.method === "DELETE" && + session.user.id === Number(req.query.id) + ) { + console.log(req.body); + const updated = await deleteUserById(session.user.id, req.body); + return res.status(updated.status).json({ response: updated.response }); } }