Files
Homarr/src/server/api/routers/user.ts

189 lines
4.6 KiB
TypeScript
Raw Normal View History

import { TRPCError } from '@trpc/server';
import bcrypt from 'bcrypt';
import { z } from 'zod';
import { hashPassword } from '~/utils/security';
import { colorSchemeParser, signUpFormSchema } from '~/validations/user';
2023-07-29 11:35:34 +02:00
import { COOKIE_COLOR_SCHEME_KEY, COOKIE_LOCALE_KEY } from '../../../../data/constants';
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
export const userRouter = createTRPCRouter({
register: publicProcedure
.input(
signUpFormSchema.and(
z.object({
registerToken: z.string(),
})
)
)
.mutation(async ({ ctx, input }) => {
const token = await ctx.prisma.registrationToken.findUnique({
where: {
token: input.registerToken,
},
});
if (!token || token.expires < new Date()) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'Invalid registration token',
});
}
const existingUser = await ctx.prisma.user.findFirst({
where: {
name: input.username,
},
});
if (existingUser) {
throw new TRPCError({
code: 'CONFLICT',
message: 'User already exists',
});
}
const salt = bcrypt.genSaltSync(10);
const hashedPassword = hashPassword(input.password, salt);
const user = await ctx.prisma.user.create({
data: {
name: input.username,
password: hashedPassword,
salt: salt,
settings: {
create: {
2023-07-29 11:35:34 +02:00
colorScheme: colorSchemeParser.parse(ctx.cookies[COOKIE_COLOR_SCHEME_KEY]),
language: ctx.cookies[COOKIE_LOCALE_KEY] ?? 'en',
},
},
},
});
await ctx.prisma.registrationToken.delete({
where: {
id: token.id,
},
});
return {
id: user.id,
name: user.name,
};
}),
changeColorScheme: protectedProcedure
.input(
z.object({
colorScheme: colorSchemeParser,
})
)
.mutation(async ({ ctx, input }) => {
await ctx.prisma.user.update({
where: {
id: ctx.session?.user?.id,
},
data: {
settings: {
update: {
colorScheme: input.colorScheme,
},
},
},
});
}),
2023-07-29 11:35:34 +02:00
changeLanguage: protectedProcedure
.input(
z.object({
language: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
await ctx.prisma.user.update({
where: {
id: ctx.session?.user?.id,
},
data: {
settings: {
update: {
language: input.language,
},
},
},
});
}),
2023-07-29 16:08:58 +02:00
getWithSettings: protectedProcedure.query(async ({ ctx, input }) => {
2023-07-29 14:30:19 +02:00
const user = await ctx.prisma.user.findUnique({
where: {
id: ctx.session?.user?.id,
},
include: {
settings: true,
},
});
if (!user || !user.settings) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'User not found',
});
}
return {
id: user.id,
name: user.name,
settings: user.settings,
};
}),
2023-07-29 16:08:58 +02:00
getAll: publicProcedure
.input(
z.object({
limit: z.number().min(1).max(100).nullish(),
2023-07-29 17:29:57 +02:00
cursor: z.string().nullish(),
2023-07-29 16:08:58 +02:00
})
)
.query(async ({ ctx, input }) => {
const limit = input.limit ?? 50;
const cursor = input.cursor;
const users = await ctx.prisma.user.findMany({
take: limit + 1, // get an extra item at the end which we'll use as next cursor
2023-07-29 17:29:57 +02:00
cursor: cursor ? { id: cursor } : undefined,
2023-07-29 16:08:58 +02:00
});
let nextCursor: typeof cursor | undefined = undefined;
if (users.length > limit) {
const nextItem = users.pop();
2023-07-29 17:29:57 +02:00
nextCursor = nextItem!.id;
2023-07-29 16:08:58 +02:00
}
return {
users: users.map((user) => ({
id: user.id,
name: user.name,
email: user.email,
2023-07-29 17:29:57 +02:00
emailVerified: user.emailVerified,
2023-07-29 16:08:58 +02:00
})),
nextCursor,
};
}),
2023-07-29 17:29:57 +02:00
createUser: publicProcedure
.input(
z.object({
username: z.string(),
email: z.string().email().optional(),
password: z.string().min(8).max(100),
})
)
.mutation(async ({ ctx, input }) => {
const salt = bcrypt.genSaltSync(10);
const hashedPassword = hashPassword(input.password, salt);
await ctx.prisma.user.create({
data: {
name: input.username,
email: input.email,
password: hashedPassword,
salt: salt,
},
});
}),
});