Files
home/Projects/kompose/news/apps/backend/src/user/mutation.ts

198 lines
4.6 KiB
TypeScript
Raw Normal View History

2025-10-08 10:35:48 +02:00
import { z } from "zod"
import { publicProcedure, authProcedure } from "../trpc"
import { prisma } from "../utils/prisma"
import { comparePasswords, generateToken, hashPassword } from "../utils/auth"
import { TRPCError } from "@trpc/server"
const signUpSchema = z.object({
email: z.string().email().min(1, "Email is required"),
password: z.string().min(1, "Password is required"),
name: z.string().min(1, "Name is required"),
})
export const signup = publicProcedure
.input(signUpSchema)
.mutation(async ({ input }) => {
const { email, password, name } = input
if (await prisma.user.count()) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Bad request",
})
}
const existingUser = await prisma.user.findUnique({
where: {
email,
},
})
if (existingUser) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `User with email ${email} already exists`,
})
}
const hashedPassword = await hashPassword(password)
const user = await prisma.user.create({
data: {
email,
password: hashedPassword,
name,
},
select: {
id: true,
pwdVersion: true,
},
})
const token = generateToken(user.id, user.pwdVersion)
return {
token,
}
})
export const login = publicProcedure
.input(
z.object({
email: z.string().email().min(1, "Email is required"),
password: z.string().min(1, "Password is required"),
})
)
.mutation(async ({ input }) => {
const { email, password } = input
const user = await prisma.user.findUnique({
where: { email },
select: {
id: true,
password: true,
pwdVersion: true,
UserOrganizations: true,
},
})
if (!user) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Invalid credentials",
})
}
const isValidPassword = await comparePasswords(password, user.password)
if (!isValidPassword) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Invalid credentials",
})
}
const token = generateToken(user.id, user.pwdVersion)
return {
token,
user,
}
})
const updateProfileSchema = z.object({
name: z.string().min(1, "Name is required."),
email: z.string().email("Invalid email address.").toLowerCase(),
})
export const updateProfile = authProcedure
.input(updateProfileSchema)
.mutation(async ({ ctx, input }) => {
const { name, email } = input
const userId = ctx.user.id
const currentUser = await prisma.user.findUnique({
where: { id: userId },
})
if (currentUser?.email !== email) {
const existingUserWithEmail = await prisma.user.findFirst({
where: {
email: email,
id: { not: userId },
},
})
if (existingUserWithEmail) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Email address is already in use by another account.",
})
}
}
const updatedUser = await prisma.user.update({
where: { id: userId },
data: {
name,
email: email.toLowerCase(),
},
select: {
id: true,
name: true,
email: true,
UserOrganizations: {
include: {
Organization: true,
},
},
},
})
return { user: updatedUser }
})
const changePasswordSchema = z.object({
currentPassword: z.string(),
newPassword: z.string().min(8, "New password must be at least 8 characters."),
})
export const changePassword = authProcedure
.input(changePasswordSchema)
.mutation(async ({ ctx, input }) => {
const userId = ctx.user.id
const { currentPassword, newPassword } = input
const user = await prisma.user.findUnique({
where: { id: userId },
select: { password: true, pwdVersion: true },
})
if (!user) {
throw new TRPCError({ code: "NOT_FOUND", message: "User not found." })
}
const isValidPassword = await comparePasswords(
currentPassword,
user.password
)
if (!isValidPassword) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Incorrect current password.",
})
}
const hashedNewPassword = await hashPassword(newPassword)
const newPwdVersion = (user.pwdVersion || 0) + 1
await prisma.user.update({
where: { id: userId },
data: {
password: hashedNewPassword,
pwdVersion: newPwdVersion,
},
})
const newToken = generateToken(userId, newPwdVersion)
return { success: true, token: newToken }
})