chore: refactor routes (#1992)

This commit is contained in:
David Nguyen
2025-08-25 21:00:35 +10:00
committed by GitHub
parent 7eb882aea8
commit 44f5da95b3
16 changed files with 89 additions and 43 deletions

View File

@ -3,12 +3,12 @@ import { useState } from 'react';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import type { User } from '@prisma/client';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { import {
@ -25,7 +25,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export type AdminUserDeleteDialogProps = { export type AdminUserDeleteDialogProps = {
className?: string; className?: string;
user: User; user: TGetUserResponse;
}; };
export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialogProps) => { export const AdminUserDeleteDialog = ({ className, user }: AdminUserDeleteDialogProps) => {

View File

@ -3,11 +3,11 @@ import { useState } from 'react';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import type { User } from '@prisma/client';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { import {
@ -24,7 +24,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export type AdminUserDisableDialogProps = { export type AdminUserDisableDialogProps = {
className?: string; className?: string;
userToDisable: User; userToDisable: TGetUserResponse;
}; };
export const AdminUserDisableDialog = ({ export const AdminUserDisableDialog = ({

View File

@ -3,11 +3,11 @@ import { useState } from 'react';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import type { User } from '@prisma/client';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { import {
@ -24,7 +24,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export type AdminUserEnableDialogProps = { export type AdminUserEnableDialogProps = {
className?: string; className?: string;
userToEnable: User; userToEnable: TGetUserResponse;
}; };
export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnableDialogProps) => { export const AdminUserEnableDialog = ({ className, userToEnable }: AdminUserEnableDialogProps) => {

View File

@ -3,12 +3,12 @@ import { useState } from 'react';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import type { User } from '@prisma/client';
import { useRevalidator } from 'react-router'; import { useRevalidator } from 'react-router';
import { match } from 'ts-pattern'; import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { import {
@ -25,7 +25,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
export type AdminUserResetTwoFactorDialogProps = { export type AdminUserResetTwoFactorDialogProps = {
className?: string; className?: string;
user: User; user: TGetUserResponse;
}; };
export const AdminUserResetTwoFactorDialog = ({ export const AdminUserResetTwoFactorDialog = ({

View File

@ -2,13 +2,13 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react'; import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro';
import type { User } from '@prisma/client';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useRevalidator } from 'react-router'; import { useRevalidator } from 'react-router';
import { Link } from 'react-router'; import { Link } from 'react-router';
import type { z } from 'zod'; import type { z } from 'zod';
import { trpc } from '@documenso/trpc/react'; import { trpc } from '@documenso/trpc/react';
import type { TGetUserResponse } from '@documenso/trpc/server/admin-router/get-user.types';
import { ZUpdateUserRequestSchema } from '@documenso/trpc/server/admin-router/update-user.types'; import { ZUpdateUserRequestSchema } from '@documenso/trpc/server/admin-router/update-user.types';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { import {
@ -38,7 +38,7 @@ const ZUserFormSchema = ZUpdateUserRequestSchema.omit({ id: true });
type TUserFormSchema = z.infer<typeof ZUserFormSchema>; type TUserFormSchema = z.infer<typeof ZUserFormSchema>;
export default function UserPage({ params }: { params: { id: number } }) { export default function UserPage({ params }: { params: { id: number } }) {
const { data: user, isLoading: isLoadingUser } = trpc.profile.getUser.useQuery( const { data: user, isLoading: isLoadingUser } = trpc.admin.user.get.useQuery(
{ {
id: Number(params.id), id: Number(params.id),
}, },
@ -78,7 +78,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
return <AdminUserPage user={user} />; return <AdminUserPage user={user} />;
} }
const AdminUserPage = ({ user }: { user: User }) => { const AdminUserPage = ({ user }: { user: TGetUserResponse }) => {
const { _ } = useLingui(); const { _ } = useLingui();
const { toast } = useToast(); const { toast } = useToast();
const { revalidate } = useRevalidator(); const { revalidate } = useRevalidator();

View File

@ -45,6 +45,9 @@ export async function loader({ params, request }: Route.LoaderArgs) {
mode: 'insensitive', mode: 'insensitive',
}, },
}, },
select: {
id: true,
},
}); });
// Directly convert the team member invite to a team member if they already have an account. // Directly convert the team member invite to a team member if they already have an account.

View File

@ -111,6 +111,10 @@ export const handleOAuthCallbackUrl = async (options: HandleOAuthCallbackUrlOpti
where: { where: {
email: email, email: email,
}, },
select: {
id: true,
emailVerified: true,
},
}); });
// Handle existing user but no account. // Handle existing user but no account.

View File

@ -42,6 +42,11 @@ export const run = async ({
where: { where: {
id: userId, id: userId,
}, },
select: {
id: true,
email: true,
name: true,
},
}), }),
prisma.document.findFirstOrThrow({ prisma.document.findFirstOrThrow({
where: { where: {

View File

@ -10,12 +10,6 @@ export type UpdateUserOptions = {
}; };
export const updateUser = async ({ id, name, email, roles }: UpdateUserOptions) => { export const updateUser = async ({ id, name, email, roles }: UpdateUserOptions) => {
await prisma.user.findFirstOrThrow({
where: {
id,
},
});
await prisma.user.update({ await prisma.user.update({
where: { where: {
id, id,

View File

@ -1,13 +1,31 @@
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { AppError, AppErrorCode } from '../../errors/app-error';
export interface GetUserByIdOptions { export interface GetUserByIdOptions {
id: number; id: number;
} }
export const getUserById = async ({ id }: GetUserByIdOptions) => { export const getUserById = async ({ id }: GetUserByIdOptions) => {
return await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirst({
where: { where: {
id, id,
}, },
select: {
id: true,
name: true,
email: true,
emailVerified: true,
roles: true,
disabled: true,
twoFactorEnabled: true,
signature: true,
},
}); });
if (!user) {
throw new AppError(AppErrorCode.NOT_FOUND);
}
return user;
}; };

View File

@ -24,7 +24,7 @@ export const updateProfile = async ({
}, },
}); });
return await prisma.$transaction(async (tx) => { await prisma.$transaction(async (tx) => {
await tx.userSecurityAuditLog.create({ await tx.userSecurityAuditLog.create({
data: { data: {
userId, userId,
@ -34,7 +34,7 @@ export const updateProfile = async ({
}, },
}); });
return await tx.user.update({ await tx.user.update({
where: { where: {
id: userId, id: userId,
}, },

View File

@ -0,0 +1,19 @@
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
import { adminProcedure } from '../trpc';
import { ZGetUserRequestSchema, ZGetUserResponseSchema } from './get-user.types';
export const getUserRoute = adminProcedure
.input(ZGetUserRequestSchema)
.output(ZGetUserResponseSchema)
.query(async ({ input, ctx }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
});
return await getUserById({ id });
});

View File

@ -0,0 +1,21 @@
import { z } from 'zod';
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
export const ZGetUserRequestSchema = z.object({
id: z.number().min(1),
});
export const ZGetUserResponseSchema = UserSchema.pick({
id: true,
name: true,
email: true,
emailVerified: true,
roles: true,
disabled: true,
twoFactorEnabled: true,
signature: true,
});
export type TGetUserRequest = z.infer<typeof ZGetUserRequestSchema>;
export type TGetUserResponse = z.infer<typeof ZGetUserResponseSchema>;

View File

@ -11,6 +11,7 @@ import { findAdminOrganisationsRoute } from './find-admin-organisations';
import { findDocumentsRoute } from './find-documents'; import { findDocumentsRoute } from './find-documents';
import { findSubscriptionClaimsRoute } from './find-subscription-claims'; import { findSubscriptionClaimsRoute } from './find-subscription-claims';
import { getAdminOrganisationRoute } from './get-admin-organisation'; import { getAdminOrganisationRoute } from './get-admin-organisation';
import { getUserRoute } from './get-user';
import { resealDocumentRoute } from './reseal-document'; import { resealDocumentRoute } from './reseal-document';
import { resetTwoFactorRoute } from './reset-two-factor-authentication'; import { resetTwoFactorRoute } from './reset-two-factor-authentication';
import { updateAdminOrganisationRoute } from './update-admin-organisation'; import { updateAdminOrganisationRoute } from './update-admin-organisation';
@ -36,6 +37,7 @@ export const adminRouter = router({
createCustomer: createStripeCustomerRoute, createCustomer: createStripeCustomerRoute,
}, },
user: { user: {
get: getUserRoute,
update: updateUserRoute, update: updateUserRoute,
delete: deleteUserRoute, delete: deleteUserRoute,
enable: enableUserRoute, enable: enableUserRoute,

View File

@ -3,14 +3,12 @@ import type { SetAvatarImageOptions } from '@documenso/lib/server-only/profile/s
import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image'; import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image';
import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs'; import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs';
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
import { submitSupportTicket } from '@documenso/lib/server-only/user/submit-support-ticket'; import { submitSupportTicket } from '@documenso/lib/server-only/user/submit-support-ticket';
import { updateProfile } from '@documenso/lib/server-only/user/update-profile'; import { updateProfile } from '@documenso/lib/server-only/user/update-profile';
import { adminProcedure, authenticatedProcedure, router } from '../trpc'; import { authenticatedProcedure, router } from '../trpc';
import { import {
ZFindUserSecurityAuditLogsSchema, ZFindUserSecurityAuditLogsSchema,
ZRetrieveUserByIdQuerySchema,
ZSetProfileImageMutationSchema, ZSetProfileImageMutationSchema,
ZSubmitSupportTicketMutationSchema, ZSubmitSupportTicketMutationSchema,
ZUpdateProfileMutationSchema, ZUpdateProfileMutationSchema,
@ -26,24 +24,12 @@ export const profileRouter = router({
}); });
}), }),
getUser: adminProcedure.input(ZRetrieveUserByIdQuerySchema).query(async ({ input, ctx }) => {
const { id } = input;
ctx.logger.info({
input: {
id,
},
});
return await getUserById({ id });
}),
updateProfile: authenticatedProcedure updateProfile: authenticatedProcedure
.input(ZUpdateProfileMutationSchema) .input(ZUpdateProfileMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { name, signature } = input; const { name, signature } = input;
return await updateProfile({ await updateProfile({
userId: ctx.user.id, userId: ctx.user.id,
name, name,
signature, signature,
@ -52,7 +38,7 @@ export const profileRouter = router({
}), }),
deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => { deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => {
return await deleteUser({ await deleteUser({
id: ctx.user.id, id: ctx.user.id,
}); });
}), }),

View File

@ -7,12 +7,6 @@ export const ZFindUserSecurityAuditLogsSchema = z.object({
export type TFindUserSecurityAuditLogsSchema = z.infer<typeof ZFindUserSecurityAuditLogsSchema>; export type TFindUserSecurityAuditLogsSchema = z.infer<typeof ZFindUserSecurityAuditLogsSchema>;
export const ZRetrieveUserByIdQuerySchema = z.object({
id: z.number().min(1),
});
export type TRetrieveUserByIdQuerySchema = z.infer<typeof ZRetrieveUserByIdQuerySchema>;
export const ZUpdateProfileMutationSchema = z.object({ export const ZUpdateProfileMutationSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
signature: z.string(), signature: z.string(),