mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-17 18:51:14 +01:00
232 lines
5.4 KiB
TypeScript
232 lines
5.4 KiB
TypeScript
import { TRPCError } from '@trpc/server';
|
|
import bcrypt from 'bcrypt';
|
|
import { z } from 'zod';
|
|
import { hashPassword } from '~/utils/security';
|
|
import {
|
|
colorSchemeParser,
|
|
createNewUserSchema,
|
|
signUpFormSchema,
|
|
updateSettingsValidationSchema,
|
|
} from '~/validations/user';
|
|
|
|
import { COOKIE_COLOR_SCHEME_KEY, COOKIE_LOCALE_KEY } from '../../../../data/constants';
|
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
|
|
|
|
export const userRouter = createTRPCRouter({
|
|
createFromInvite: publicProcedure
|
|
.input(
|
|
signUpFormSchema.and(
|
|
z.object({
|
|
inviteToken: z.string(),
|
|
})
|
|
)
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
const token = await ctx.prisma.invite.findUnique({
|
|
where: {
|
|
token: input.inviteToken,
|
|
},
|
|
});
|
|
|
|
if (!token || token.expires < new Date()) {
|
|
throw new TRPCError({
|
|
code: 'FORBIDDEN',
|
|
message: 'Invalid invite 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: {
|
|
colorScheme: colorSchemeParser.parse(ctx.cookies[COOKIE_COLOR_SCHEME_KEY]),
|
|
language: ctx.cookies[COOKIE_LOCALE_KEY] ?? 'en',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await ctx.prisma.invite.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,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
withSettings: protectedProcedure.query(async ({ ctx, input }) => {
|
|
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,
|
|
};
|
|
}),
|
|
|
|
updateSettings: protectedProcedure
|
|
.input(updateSettingsValidationSchema)
|
|
.mutation(async ({ ctx, input }) => {
|
|
await ctx.prisma.user.update({
|
|
where: {
|
|
id: ctx.session.user.id,
|
|
},
|
|
data: {
|
|
settings: {
|
|
update: {
|
|
disablePingPulse: input.disablePingPulse,
|
|
replacePingWithIcons: input.replaceDotsWithIcons,
|
|
language: input.language,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
all: publicProcedure
|
|
.input(
|
|
z.object({
|
|
limit: z.number().min(1).max(100).default(10),
|
|
page: z.number().min(0),
|
|
search: z
|
|
.string()
|
|
.optional()
|
|
.transform((value) => (value === '' ? undefined : value)),
|
|
})
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const limit = input.limit;
|
|
const users = await ctx.prisma.user.findMany({
|
|
take: limit + 1,
|
|
skip: limit * input.page,
|
|
where: {
|
|
name: {
|
|
contains: input.search,
|
|
},
|
|
},
|
|
});
|
|
|
|
const countUsers = await ctx.prisma.user.count({
|
|
where: {
|
|
name: {
|
|
contains: input.search,
|
|
},
|
|
},
|
|
});
|
|
|
|
return {
|
|
users: users.map((user) => ({
|
|
id: user.id,
|
|
name: user.name,
|
|
email: user.email,
|
|
emailVerified: user.emailVerified,
|
|
})),
|
|
countPages: Math.ceil(countUsers / limit),
|
|
};
|
|
}),
|
|
create: publicProcedure.input(createNewUserSchema).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,
|
|
settings: {
|
|
create: {},
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
deleteUser: publicProcedure
|
|
.input(
|
|
z.object({
|
|
userId: z.string(),
|
|
})
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
await ctx.prisma.user.delete({
|
|
where: {
|
|
id: input.userId,
|
|
},
|
|
});
|
|
}),
|
|
});
|