mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 17:51:49 +10:00
fix: merge conflicts
This commit is contained in:
28
packages/trpc/server/admin-router/delete-document.ts
Normal file
28
packages/trpc/server/admin-router/delete-document.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { sendDeleteEmail } from '@documenso/lib/server-only/document/send-delete-email';
|
||||
import { superDeleteDocument } from '@documenso/lib/server-only/document/super-delete-document';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteDocumentRequestSchema,
|
||||
ZDeleteDocumentResponseSchema,
|
||||
} from './delete-document.types';
|
||||
|
||||
export const deleteDocumentRoute = adminProcedure
|
||||
.input(ZDeleteDocumentRequestSchema)
|
||||
.output(ZDeleteDocumentResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, reason } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
await sendDeleteEmail({ documentId: id, reason });
|
||||
|
||||
await superDeleteDocument({
|
||||
id,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
});
|
||||
11
packages/trpc/server/admin-router/delete-document.types.ts
Normal file
11
packages/trpc/server/admin-router/delete-document.types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteDocumentRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
reason: z.string(),
|
||||
});
|
||||
|
||||
export const ZDeleteDocumentResponseSchema = z.void();
|
||||
|
||||
export type TDeleteDocumentRequest = z.infer<typeof ZDeleteDocumentRequestSchema>;
|
||||
export type TDeleteDocumentResponse = z.infer<typeof ZDeleteDocumentResponseSchema>;
|
||||
19
packages/trpc/server/admin-router/delete-user.ts
Normal file
19
packages/trpc/server/admin-router/delete-user.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZDeleteUserRequestSchema, ZDeleteUserResponseSchema } from './delete-user.types';
|
||||
|
||||
export const deleteUserRoute = adminProcedure
|
||||
.input(ZDeleteUserRequestSchema)
|
||||
.output(ZDeleteUserResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteUser({ id });
|
||||
});
|
||||
10
packages/trpc/server/admin-router/delete-user.types.ts
Normal file
10
packages/trpc/server/admin-router/delete-user.types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteUserRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZDeleteUserResponseSchema = z.void();
|
||||
|
||||
export type TDeleteUserRequest = z.infer<typeof ZDeleteUserRequestSchema>;
|
||||
export type TDeleteUserResponse = z.infer<typeof ZDeleteUserResponseSchema>;
|
||||
29
packages/trpc/server/admin-router/disable-user.ts
Normal file
29
packages/trpc/server/admin-router/disable-user.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { disableUser } from '@documenso/lib/server-only/user/disable-user';
|
||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZDisableUserRequestSchema, ZDisableUserResponseSchema } from './disable-user.types';
|
||||
|
||||
export const disableUserRoute = adminProcedure
|
||||
.input(ZDisableUserRequestSchema)
|
||||
.output(ZDisableUserResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
await disableUser({ id });
|
||||
});
|
||||
10
packages/trpc/server/admin-router/disable-user.types.ts
Normal file
10
packages/trpc/server/admin-router/disable-user.types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDisableUserRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZDisableUserResponseSchema = z.void();
|
||||
|
||||
export type TDisableUserRequest = z.infer<typeof ZDisableUserRequestSchema>;
|
||||
export type TDisableUserResponse = z.infer<typeof ZDisableUserResponseSchema>;
|
||||
29
packages/trpc/server/admin-router/enable-user.ts
Normal file
29
packages/trpc/server/admin-router/enable-user.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { enableUser } from '@documenso/lib/server-only/user/enable-user';
|
||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZEnableUserRequestSchema, ZEnableUserResponseSchema } from './enable-user.types';
|
||||
|
||||
export const enableUserRoute = adminProcedure
|
||||
.input(ZEnableUserRequestSchema)
|
||||
.output(ZEnableUserResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
await enableUser({ id });
|
||||
});
|
||||
10
packages/trpc/server/admin-router/enable-user.types.ts
Normal file
10
packages/trpc/server/admin-router/enable-user.types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZEnableUserRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZEnableUserResponseSchema = z.void();
|
||||
|
||||
export type TEnableUserRequest = z.infer<typeof ZEnableUserRequestSchema>;
|
||||
export type TEnableUserResponse = z.infer<typeof ZEnableUserResponseSchema>;
|
||||
13
packages/trpc/server/admin-router/find-documents.ts
Normal file
13
packages/trpc/server/admin-router/find-documents.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZFindDocumentsRequestSchema, ZFindDocumentsResponseSchema } from './find-documents.types';
|
||||
|
||||
export const findDocumentsRoute = adminProcedure
|
||||
.input(ZFindDocumentsRequestSchema)
|
||||
.output(ZFindDocumentsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { query, page, perPage } = input;
|
||||
|
||||
return await findDocuments({ query, page, perPage });
|
||||
});
|
||||
17
packages/trpc/server/admin-router/find-documents.types.ts
Normal file
17
packages/trpc/server/admin-router/find-documents.types.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
perPage: z.number().optional().default(20),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.omit({
|
||||
team: true,
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export type TFindDocumentsRequest = z.infer<typeof ZFindDocumentsRequestSchema>;
|
||||
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
|
||||
19
packages/trpc/server/admin-router/get-user.ts
Normal file
19
packages/trpc/server/admin-router/get-user.ts
Normal 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 });
|
||||
});
|
||||
21
packages/trpc/server/admin-router/get-user.types.ts
Normal file
21
packages/trpc/server/admin-router/get-user.types.ts
Normal 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>;
|
||||
28
packages/trpc/server/admin-router/reseal-document.ts
Normal file
28
packages/trpc/server/admin-router/reseal-document.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZResealDocumentRequestSchema,
|
||||
ZResealDocumentResponseSchema,
|
||||
} from './reseal-document.types';
|
||||
|
||||
export const resealDocumentRoute = adminProcedure
|
||||
.input(ZResealDocumentRequestSchema)
|
||||
.output(ZResealDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getEntireDocument({ id });
|
||||
|
||||
const isResealing = isDocumentCompleted(document.status);
|
||||
|
||||
await sealDocument({ documentId: id, isResealing });
|
||||
});
|
||||
10
packages/trpc/server/admin-router/reseal-document.types.ts
Normal file
10
packages/trpc/server/admin-router/reseal-document.types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZResealDocumentRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZResealDocumentResponseSchema = z.void();
|
||||
|
||||
export type TResealDocumentRequest = z.infer<typeof ZResealDocumentRequestSchema>;
|
||||
export type TResealDocumentResponse = z.infer<typeof ZResealDocumentResponseSchema>;
|
||||
@ -0,0 +1,50 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZResetTwoFactorRequestSchema,
|
||||
ZResetTwoFactorResponseSchema,
|
||||
} from './reset-two-factor-authentication.types';
|
||||
|
||||
export const resetTwoFactorRoute = adminProcedure
|
||||
.input(ZResetTwoFactorRequestSchema)
|
||||
.output(ZResetTwoFactorResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { userId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
return await resetTwoFactor({ userId });
|
||||
});
|
||||
|
||||
export type ResetTwoFactorOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export const resetTwoFactor = async ({ userId }: ResetTwoFactorOptions) => {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, { message: 'User not found' });
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
twoFactorEnabled: false,
|
||||
twoFactorBackupCodes: null,
|
||||
twoFactorSecret: null,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZResetTwoFactorRequestSchema = z.object({
|
||||
userId: z.number(),
|
||||
});
|
||||
|
||||
export const ZResetTwoFactorResponseSchema = z.void();
|
||||
|
||||
export type TResetTwoFactorRequest = z.infer<typeof ZResetTwoFactorRequestSchema>;
|
||||
export type TResetTwoFactorResponse = z.infer<typeof ZResetTwoFactorResponseSchema>;
|
||||
@ -1,39 +1,24 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { findDocuments } from '@documenso/lib/server-only/admin/get-all-documents';
|
||||
import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient';
|
||||
import { updateUser } from '@documenso/lib/server-only/admin/update-user';
|
||||
import { sealDocument } from '@documenso/lib/server-only/document/seal-document';
|
||||
import { sendDeleteEmail } from '@documenso/lib/server-only/document/send-delete-email';
|
||||
import { superDeleteDocument } from '@documenso/lib/server-only/document/super-delete-document';
|
||||
import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upsert-site-setting';
|
||||
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
|
||||
import { disableUser } from '@documenso/lib/server-only/user/disable-user';
|
||||
import { enableUser } from '@documenso/lib/server-only/user/enable-user';
|
||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { adminProcedure, router } from '../trpc';
|
||||
import { router } from '../trpc';
|
||||
import { createAdminOrganisationRoute } from './create-admin-organisation';
|
||||
import { createStripeCustomerRoute } from './create-stripe-customer';
|
||||
import { createSubscriptionClaimRoute } from './create-subscription-claim';
|
||||
import { deleteDocumentRoute } from './delete-document';
|
||||
import { deleteSubscriptionClaimRoute } from './delete-subscription-claim';
|
||||
import { deleteUserRoute } from './delete-user';
|
||||
import { disableUserRoute } from './disable-user';
|
||||
import { enableUserRoute } from './enable-user';
|
||||
import { findAdminOrganisationsRoute } from './find-admin-organisations';
|
||||
import { findDocumentsRoute } from './find-documents';
|
||||
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
|
||||
import { getAdminOrganisationRoute } from './get-admin-organisation';
|
||||
import {
|
||||
ZAdminDeleteDocumentMutationSchema,
|
||||
ZAdminDeleteUserMutationSchema,
|
||||
ZAdminDisableUserMutationSchema,
|
||||
ZAdminEnableUserMutationSchema,
|
||||
ZAdminFindDocumentsQuerySchema,
|
||||
ZAdminResealDocumentMutationSchema,
|
||||
ZAdminUpdateProfileMutationSchema,
|
||||
ZAdminUpdateRecipientMutationSchema,
|
||||
ZAdminUpdateSiteSettingMutationSchema,
|
||||
} from './schema';
|
||||
import { getUserRoute } from './get-user';
|
||||
import { resealDocumentRoute } from './reseal-document';
|
||||
import { resetTwoFactorRoute } from './reset-two-factor-authentication';
|
||||
import { updateAdminOrganisationRoute } from './update-admin-organisation';
|
||||
import { updateRecipientRoute } from './update-recipient';
|
||||
import { updateSiteSettingRoute } from './update-site-setting';
|
||||
import { updateSubscriptionClaimRoute } from './update-subscription-claim';
|
||||
import { updateUserRoute } from './update-user';
|
||||
|
||||
export const adminRouter = router({
|
||||
organisation: {
|
||||
@ -51,154 +36,21 @@ export const adminRouter = router({
|
||||
stripe: {
|
||||
createCustomer: createStripeCustomerRoute,
|
||||
},
|
||||
|
||||
// Todo: migrate old routes
|
||||
findDocuments: adminProcedure.input(ZAdminFindDocumentsQuerySchema).query(async ({ input }) => {
|
||||
const { query, page, perPage } = input;
|
||||
|
||||
return await findDocuments({ query, page, perPage });
|
||||
}),
|
||||
|
||||
updateUser: adminProcedure
|
||||
.input(ZAdminUpdateProfileMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, name, email, roles } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
roles,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateUser({ id, name, email, roles });
|
||||
}),
|
||||
|
||||
updateRecipient: adminProcedure
|
||||
.input(ZAdminUpdateRecipientMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, name, email } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return await updateRecipient({ id, name, email });
|
||||
}),
|
||||
|
||||
updateSiteSetting: adminProcedure
|
||||
.input(ZAdminUpdateSiteSettingMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, enabled, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return await upsertSiteSetting({
|
||||
id,
|
||||
enabled,
|
||||
data,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
resealDocument: adminProcedure
|
||||
.input(ZAdminResealDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getEntireDocument({ id });
|
||||
|
||||
const isResealing = isDocumentCompleted(document.status);
|
||||
|
||||
return await sealDocument({ documentId: id, isResealing });
|
||||
}),
|
||||
|
||||
enableUser: adminProcedure
|
||||
.input(ZAdminEnableUserMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
return await enableUser({ id });
|
||||
}),
|
||||
|
||||
disableUser: adminProcedure
|
||||
.input(ZAdminDisableUserMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const user = await getUserById({ id }).catch(() => null);
|
||||
|
||||
if (!user) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
return await disableUser({ id });
|
||||
}),
|
||||
|
||||
deleteUser: adminProcedure
|
||||
.input(ZAdminDeleteUserMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteUser({ id });
|
||||
}),
|
||||
|
||||
deleteDocument: adminProcedure
|
||||
.input(ZAdminDeleteDocumentMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, reason } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
await sendDeleteEmail({ documentId: id, reason });
|
||||
|
||||
return await superDeleteDocument({
|
||||
id,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
user: {
|
||||
get: getUserRoute,
|
||||
update: updateUserRoute,
|
||||
delete: deleteUserRoute,
|
||||
enable: enableUserRoute,
|
||||
disable: disableUserRoute,
|
||||
resetTwoFactor: resetTwoFactorRoute,
|
||||
},
|
||||
document: {
|
||||
find: findDocumentsRoute,
|
||||
delete: deleteDocumentRoute,
|
||||
reseal: resealDocumentRoute,
|
||||
},
|
||||
recipient: {
|
||||
update: updateRecipientRoute,
|
||||
},
|
||||
updateSiteSetting: updateSiteSettingRoute,
|
||||
});
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import { Role } from '@prisma/client';
|
||||
import z from 'zod';
|
||||
|
||||
import { ZSiteSettingSchema } from '@documenso/lib/server-only/site-settings/schema';
|
||||
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const ZAdminFindDocumentsQuerySchema = ZFindSearchParamsSchema.extend({
|
||||
perPage: z.number().optional().default(20),
|
||||
});
|
||||
|
||||
export type TAdminFindDocumentsQuerySchema = z.infer<typeof ZAdminFindDocumentsQuerySchema>;
|
||||
|
||||
export const ZAdminUpdateProfileMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
name: z.string().nullish(),
|
||||
email: z.string().email().optional(),
|
||||
roles: z.array(z.nativeEnum(Role)).optional(),
|
||||
});
|
||||
|
||||
export type TAdminUpdateProfileMutationSchema = z.infer<typeof ZAdminUpdateProfileMutationSchema>;
|
||||
|
||||
export const ZAdminUpdateRecipientMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
name: z.string().optional(),
|
||||
email: z.string().email().optional(),
|
||||
});
|
||||
|
||||
export type TAdminUpdateRecipientMutationSchema = z.infer<
|
||||
typeof ZAdminUpdateRecipientMutationSchema
|
||||
>;
|
||||
|
||||
export const ZAdminUpdateSiteSettingMutationSchema = ZSiteSettingSchema;
|
||||
|
||||
export type TAdminUpdateSiteSettingMutationSchema = z.infer<
|
||||
typeof ZAdminUpdateSiteSettingMutationSchema
|
||||
>;
|
||||
|
||||
export const ZAdminResealDocumentMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export type TAdminResealDocumentMutationSchema = z.infer<typeof ZAdminResealDocumentMutationSchema>;
|
||||
|
||||
export const ZAdminDeleteUserMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export type TAdminDeleteUserMutationSchema = z.infer<typeof ZAdminDeleteUserMutationSchema>;
|
||||
|
||||
export const ZAdminEnableUserMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export type TAdminEnableUserMutationSchema = z.infer<typeof ZAdminEnableUserMutationSchema>;
|
||||
|
||||
export const ZAdminDisableUserMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
});
|
||||
|
||||
export type TAdminDisableUserMutationSchema = z.infer<typeof ZAdminDisableUserMutationSchema>;
|
||||
|
||||
export const ZAdminDeleteDocumentMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
reason: z.string(),
|
||||
});
|
||||
|
||||
export type TAdminDeleteDocomentMutationSchema = z.infer<typeof ZAdminDeleteDocumentMutationSchema>;
|
||||
22
packages/trpc/server/admin-router/update-recipient.ts
Normal file
22
packages/trpc/server/admin-router/update-recipient.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateRecipientRequestSchema,
|
||||
ZUpdateRecipientResponseSchema,
|
||||
} from './update-recipient.types';
|
||||
|
||||
export const updateRecipientRoute = adminProcedure
|
||||
.input(ZUpdateRecipientRequestSchema)
|
||||
.output(ZUpdateRecipientResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, name, email } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
await updateRecipient({ id, name, email });
|
||||
});
|
||||
12
packages/trpc/server/admin-router/update-recipient.types.ts
Normal file
12
packages/trpc/server/admin-router/update-recipient.types.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZUpdateRecipientRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
name: z.string().optional(),
|
||||
email: z.string().email().optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateRecipientResponseSchema = z.void();
|
||||
|
||||
export type TUpdateRecipientRequest = z.infer<typeof ZUpdateRecipientRequestSchema>;
|
||||
export type TUpdateRecipientResponse = z.infer<typeof ZUpdateRecipientResponseSchema>;
|
||||
27
packages/trpc/server/admin-router/update-site-setting.ts
Normal file
27
packages/trpc/server/admin-router/update-site-setting.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upsert-site-setting';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateSiteSettingRequestSchema,
|
||||
ZUpdateSiteSettingResponseSchema,
|
||||
} from './update-site-setting.types';
|
||||
|
||||
export const updateSiteSettingRoute = adminProcedure
|
||||
.input(ZUpdateSiteSettingRequestSchema)
|
||||
.output(ZUpdateSiteSettingResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, enabled, data } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
await upsertSiteSetting({
|
||||
id,
|
||||
enabled,
|
||||
data,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSiteSettingSchema } from '@documenso/lib/server-only/site-settings/schema';
|
||||
|
||||
export const ZUpdateSiteSettingRequestSchema = ZSiteSettingSchema;
|
||||
|
||||
export const ZUpdateSiteSettingResponseSchema = z.void();
|
||||
|
||||
export type TUpdateSiteSettingRequest = z.infer<typeof ZUpdateSiteSettingRequestSchema>;
|
||||
export type TUpdateSiteSettingResponse = z.infer<typeof ZUpdateSiteSettingResponseSchema>;
|
||||
20
packages/trpc/server/admin-router/update-user.ts
Normal file
20
packages/trpc/server/admin-router/update-user.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { updateUser } from '@documenso/lib/server-only/admin/update-user';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZUpdateUserRequestSchema, ZUpdateUserResponseSchema } from './update-user.types';
|
||||
|
||||
export const updateUserRoute = adminProcedure
|
||||
.input(ZUpdateUserRequestSchema)
|
||||
.output(ZUpdateUserResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, name, email, roles } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
roles,
|
||||
},
|
||||
});
|
||||
|
||||
await updateUser({ id, name, email, roles });
|
||||
});
|
||||
14
packages/trpc/server/admin-router/update-user.types.ts
Normal file
14
packages/trpc/server/admin-router/update-user.types.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Role } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZUpdateUserRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
name: z.string().nullish(),
|
||||
email: z.string().email().optional(),
|
||||
roles: z.array(z.nativeEnum(Role)).optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateUserResponseSchema = z.void();
|
||||
|
||||
export type TUpdateUserRequest = z.infer<typeof ZUpdateUserRequestSchema>;
|
||||
export type TUpdateUserResponse = z.infer<typeof ZUpdateUserResponseSchema>;
|
||||
27
packages/trpc/server/api-token-router/create-api-token.ts
Normal file
27
packages/trpc/server/api-token-router/create-api-token.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateApiTokenRequestSchema,
|
||||
ZCreateApiTokenResponseSchema,
|
||||
} from './create-api-token.types';
|
||||
|
||||
export const createApiTokenRoute = authenticatedProcedure
|
||||
.input(ZCreateApiTokenRequestSchema)
|
||||
.output(ZCreateApiTokenResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { tokenName, teamId, expirationDate } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createApiToken({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
tokenName,
|
||||
expiresIn: expirationDate,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateApiTokenRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
tokenName: z.string().min(3, { message: 'The token name should be 3 characters or longer' }),
|
||||
expirationDate: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const ZCreateApiTokenResponseSchema = z.object({
|
||||
id: z.number(),
|
||||
token: z.string(),
|
||||
});
|
||||
27
packages/trpc/server/api-token-router/delete-api-token.ts
Normal file
27
packages/trpc/server/api-token-router/delete-api-token.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { deleteTokenById } from '@documenso/lib/server-only/public-api/delete-api-token-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteApiTokenRequestSchema,
|
||||
ZDeleteApiTokenResponseSchema,
|
||||
} from './delete-api-token.types';
|
||||
|
||||
export const deleteApiTokenRoute = authenticatedProcedure
|
||||
.input(ZDeleteApiTokenRequestSchema)
|
||||
.output(ZDeleteApiTokenResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTokenById({
|
||||
id,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteApiTokenRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteApiTokenResponseSchema = z.void();
|
||||
19
packages/trpc/server/api-token-router/get-api-tokens.ts
Normal file
19
packages/trpc/server/api-token-router/get-api-tokens.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { getApiTokens } from '@documenso/lib/server-only/public-api/get-api-tokens';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZGetApiTokensRequestSchema, ZGetApiTokensResponseSchema } from './get-api-tokens.types';
|
||||
|
||||
export const getApiTokensRoute = authenticatedProcedure
|
||||
.input(ZGetApiTokensRequestSchema)
|
||||
.output(ZGetApiTokensResponseSchema)
|
||||
.query(async ({ ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getApiTokens({ userId: ctx.user.id, teamId });
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import ApiTokenSchema from '@documenso/prisma/generated/zod/modelSchema/ApiTokenSchema';
|
||||
|
||||
export const ZGetApiTokensRequestSchema = z.void();
|
||||
|
||||
export const ZGetApiTokensResponseSchema = z.array(
|
||||
ApiTokenSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
expires: true,
|
||||
}),
|
||||
);
|
||||
|
||||
export type TGetApiTokensResponse = z.infer<typeof ZGetApiTokensResponseSchema>;
|
||||
@ -1,50 +1,10 @@
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
import { deleteTokenById } from '@documenso/lib/server-only/public-api/delete-api-token-by-id';
|
||||
import { getApiTokens } from '@documenso/lib/server-only/public-api/get-api-tokens';
|
||||
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import { ZCreateTokenMutationSchema, ZDeleteTokenByIdMutationSchema } from './schema';
|
||||
import { router } from '../trpc';
|
||||
import { createApiTokenRoute } from './create-api-token';
|
||||
import { deleteApiTokenRoute } from './delete-api-token';
|
||||
import { getApiTokensRoute } from './get-api-tokens';
|
||||
|
||||
export const apiTokenRouter = router({
|
||||
getTokens: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
return await getApiTokens({ userId: ctx.user.id, teamId: ctx.teamId });
|
||||
}),
|
||||
|
||||
createToken: authenticatedProcedure
|
||||
.input(ZCreateTokenMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { tokenName, teamId, expirationDate } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await createApiToken({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
tokenName,
|
||||
expiresIn: expirationDate,
|
||||
});
|
||||
}),
|
||||
|
||||
deleteTokenById: authenticatedProcedure
|
||||
.input(ZDeleteTokenByIdMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
id,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return await deleteTokenById({
|
||||
id,
|
||||
teamId,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
create: createApiTokenRoute,
|
||||
getMany: getApiTokensRoute,
|
||||
delete: deleteApiTokenRoute,
|
||||
});
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateTokenMutationSchema = z.object({
|
||||
teamId: z.number(),
|
||||
tokenName: z.string().min(3, { message: 'The token name should be 3 characters or longer' }),
|
||||
expirationDate: z.string().nullable(),
|
||||
});
|
||||
|
||||
export type TCreateTokenMutationSchema = z.infer<typeof ZCreateTokenMutationSchema>;
|
||||
|
||||
export const ZDeleteTokenByIdMutationSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export type TDeleteTokenByIdMutationSchema = z.infer<typeof ZDeleteTokenByIdMutationSchema>;
|
||||
@ -0,0 +1,17 @@
|
||||
import { createPasskeyAuthenticationOptions } from '@documenso/lib/server-only/auth/create-passkey-authentication-options';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreatePasskeyAuthenticationOptionsRequestSchema,
|
||||
ZCreatePasskeyAuthenticationOptionsResponseSchema,
|
||||
} from './create-passkey-authentication-options.types';
|
||||
|
||||
export const createPasskeyAuthenticationOptionsRoute = authenticatedProcedure
|
||||
.input(ZCreatePasskeyAuthenticationOptionsRequestSchema)
|
||||
.output(ZCreatePasskeyAuthenticationOptionsResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return await createPasskeyAuthenticationOptions({
|
||||
userId: ctx.user.id,
|
||||
preferredPasskeyId: input?.preferredPasskeyId,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,19 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreatePasskeyAuthenticationOptionsRequestSchema = z
|
||||
.object({
|
||||
preferredPasskeyId: z.string().optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const ZCreatePasskeyAuthenticationOptionsResponseSchema = z.object({
|
||||
tokenReference: z.string(),
|
||||
options: z.any(), // PublicKeyCredentialRequestOptions type
|
||||
});
|
||||
|
||||
export type TCreatePasskeyAuthenticationOptionsRequest = z.infer<
|
||||
typeof ZCreatePasskeyAuthenticationOptionsRequestSchema
|
||||
>;
|
||||
export type TCreatePasskeyAuthenticationOptionsResponse = z.infer<
|
||||
typeof ZCreatePasskeyAuthenticationOptionsResponseSchema
|
||||
>;
|
||||
@ -0,0 +1,16 @@
|
||||
import { createPasskeyRegistrationOptions } from '@documenso/lib/server-only/auth/create-passkey-registration-options';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreatePasskeyRegistrationOptionsRequestSchema,
|
||||
ZCreatePasskeyRegistrationOptionsResponseSchema,
|
||||
} from './create-passkey-registration-options.types';
|
||||
|
||||
export const createPasskeyRegistrationOptionsRoute = authenticatedProcedure
|
||||
.input(ZCreatePasskeyRegistrationOptionsRequestSchema)
|
||||
.output(ZCreatePasskeyRegistrationOptionsResponseSchema)
|
||||
.mutation(async ({ ctx }) => {
|
||||
return await createPasskeyRegistrationOptions({
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreatePasskeyRegistrationOptionsRequestSchema = z.void();
|
||||
|
||||
export const ZCreatePasskeyRegistrationOptionsResponseSchema = z.any(); // PublicKeyCredentialCreationOptions type
|
||||
|
||||
export type TCreatePasskeyRegistrationOptionsRequest = z.infer<
|
||||
typeof ZCreatePasskeyRegistrationOptionsRequestSchema
|
||||
>;
|
||||
export type TCreatePasskeyRegistrationOptionsResponse = z.infer<
|
||||
typeof ZCreatePasskeyRegistrationOptionsResponseSchema
|
||||
>;
|
||||
@ -0,0 +1,24 @@
|
||||
import { createPasskeySigninOptions } from '@documenso/lib/server-only/auth/create-passkey-signin-options';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
|
||||
import { procedure } from '../trpc';
|
||||
import {
|
||||
ZCreatePasskeySigninOptionsRequestSchema,
|
||||
ZCreatePasskeySigninOptionsResponseSchema,
|
||||
} from './create-passkey-signin-options.types';
|
||||
|
||||
export const createPasskeySigninOptionsRoute = procedure
|
||||
.input(ZCreatePasskeySigninOptionsRequestSchema)
|
||||
.output(ZCreatePasskeySigninOptionsResponseSchema)
|
||||
.mutation(async () => {
|
||||
const sessionIdToken = nanoid(16);
|
||||
|
||||
const [sessionId] = decodeURI(sessionIdToken).split('|');
|
||||
|
||||
const options = await createPasskeySigninOptions({ sessionId });
|
||||
|
||||
return {
|
||||
options,
|
||||
sessionId,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreatePasskeySigninOptionsRequestSchema = z.void();
|
||||
|
||||
export const ZCreatePasskeySigninOptionsResponseSchema = z.object({
|
||||
options: z.any(), // PublicKeyCredentialRequestOptions type
|
||||
sessionId: z.string(),
|
||||
});
|
||||
|
||||
export type TCreatePasskeySigninOptionsRequest = z.infer<
|
||||
typeof ZCreatePasskeySigninOptionsRequestSchema
|
||||
>;
|
||||
export type TCreatePasskeySigninOptionsResponse = z.infer<
|
||||
typeof ZCreatePasskeySigninOptionsResponseSchema
|
||||
>;
|
||||
21
packages/trpc/server/auth-router/create-passkey.ts
Normal file
21
packages/trpc/server/auth-router/create-passkey.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
|
||||
|
||||
import { createPasskey } from '@documenso/lib/server-only/auth/create-passkey';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZCreatePasskeyRequestSchema, ZCreatePasskeyResponseSchema } from './create-passkey.types';
|
||||
|
||||
export const createPasskeyRoute = authenticatedProcedure
|
||||
.input(ZCreatePasskeyRequestSchema)
|
||||
.output(ZCreatePasskeyResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const verificationResponse = input.verificationResponse as RegistrationResponseJSON;
|
||||
|
||||
return await createPasskey({
|
||||
userId: ctx.user.id,
|
||||
verificationResponse,
|
||||
passkeyName: input.passkeyName,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
});
|
||||
13
packages/trpc/server/auth-router/create-passkey.types.ts
Normal file
13
packages/trpc/server/auth-router/create-passkey.types.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZRegistrationResponseJSONSchema } from '@documenso/lib/types/webauthn';
|
||||
|
||||
export const ZCreatePasskeyRequestSchema = z.object({
|
||||
passkeyName: z.string().trim().min(1),
|
||||
verificationResponse: ZRegistrationResponseJSONSchema,
|
||||
});
|
||||
|
||||
export const ZCreatePasskeyResponseSchema = z.void();
|
||||
|
||||
export type TCreatePasskeyRequest = z.infer<typeof ZCreatePasskeyRequestSchema>;
|
||||
export type TCreatePasskeyResponse = z.infer<typeof ZCreatePasskeyResponseSchema>;
|
||||
23
packages/trpc/server/auth-router/delete-passkey.ts
Normal file
23
packages/trpc/server/auth-router/delete-passkey.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { deletePasskey } from '@documenso/lib/server-only/auth/delete-passkey';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZDeletePasskeyRequestSchema, ZDeletePasskeyResponseSchema } from './delete-passkey.types';
|
||||
|
||||
export const deletePasskeyRoute = authenticatedProcedure
|
||||
.input(ZDeletePasskeyRequestSchema)
|
||||
.output(ZDeletePasskeyResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { passkeyId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
passkeyId,
|
||||
},
|
||||
});
|
||||
|
||||
await deletePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
});
|
||||
10
packages/trpc/server/auth-router/delete-passkey.types.ts
Normal file
10
packages/trpc/server/auth-router/delete-passkey.types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeletePasskeyRequestSchema = z.object({
|
||||
passkeyId: z.string().trim().min(1),
|
||||
});
|
||||
|
||||
export const ZDeletePasskeyResponseSchema = z.void();
|
||||
|
||||
export type TDeletePasskeyRequest = z.infer<typeof ZDeletePasskeyRequestSchema>;
|
||||
export type TDeletePasskeyResponse = z.infer<typeof ZDeletePasskeyResponseSchema>;
|
||||
18
packages/trpc/server/auth-router/find-passkeys.ts
Normal file
18
packages/trpc/server/auth-router/find-passkeys.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { findPasskeys } from '@documenso/lib/server-only/auth/find-passkeys';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZFindPasskeysRequestSchema, ZFindPasskeysResponseSchema } from './find-passkeys.types';
|
||||
|
||||
export const findPasskeysRoute = authenticatedProcedure
|
||||
.input(ZFindPasskeysRequestSchema)
|
||||
.output(ZFindPasskeysResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { page, perPage, orderBy } = input;
|
||||
|
||||
return await findPasskeys({
|
||||
page,
|
||||
perPage,
|
||||
orderBy,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
});
|
||||
33
packages/trpc/server/auth-router/find-passkeys.types.ts
Normal file
33
packages/trpc/server/auth-router/find-passkeys.types.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import PasskeySchema from '@documenso/prisma/generated/zod/modelSchema/PasskeySchema';
|
||||
|
||||
export const ZFindPasskeysRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
orderBy: z
|
||||
.object({
|
||||
column: z.enum(['createdAt', 'updatedAt', 'name']),
|
||||
direction: z.enum(['asc', 'desc']),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZFindPasskeysResponseSchema = ZFindResultResponse.extend({
|
||||
data: z.array(
|
||||
PasskeySchema.pick({
|
||||
id: true,
|
||||
userId: true,
|
||||
name: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
lastUsedAt: true,
|
||||
counter: true,
|
||||
credentialDeviceType: true,
|
||||
credentialBackedUp: true,
|
||||
transports: true,
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type TFindPasskeysRequest = z.infer<typeof ZFindPasskeysRequestSchema>;
|
||||
export type TFindPasskeysResponse = z.infer<typeof ZFindPasskeysResponseSchema>;
|
||||
@ -1,113 +1,20 @@
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
|
||||
|
||||
import { createPasskey } from '@documenso/lib/server-only/auth/create-passkey';
|
||||
import { createPasskeyAuthenticationOptions } from '@documenso/lib/server-only/auth/create-passkey-authentication-options';
|
||||
import { createPasskeyRegistrationOptions } from '@documenso/lib/server-only/auth/create-passkey-registration-options';
|
||||
import { createPasskeySigninOptions } from '@documenso/lib/server-only/auth/create-passkey-signin-options';
|
||||
import { deletePasskey } from '@documenso/lib/server-only/auth/delete-passkey';
|
||||
import { findPasskeys } from '@documenso/lib/server-only/auth/find-passkeys';
|
||||
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import {
|
||||
ZCreatePasskeyAuthenticationOptionsMutationSchema,
|
||||
ZCreatePasskeyMutationSchema,
|
||||
ZDeletePasskeyMutationSchema,
|
||||
ZFindPasskeysQuerySchema,
|
||||
ZUpdatePasskeyMutationSchema,
|
||||
} from './schema';
|
||||
import { router } from '../trpc';
|
||||
import { createPasskeyRoute } from './create-passkey';
|
||||
import { createPasskeyAuthenticationOptionsRoute } from './create-passkey-authentication-options';
|
||||
import { createPasskeyRegistrationOptionsRoute } from './create-passkey-registration-options';
|
||||
import { createPasskeySigninOptionsRoute } from './create-passkey-signin-options';
|
||||
import { deletePasskeyRoute } from './delete-passkey';
|
||||
import { findPasskeysRoute } from './find-passkeys';
|
||||
import { updatePasskeyRoute } from './update-passkey';
|
||||
|
||||
export const authRouter = router({
|
||||
createPasskey: authenticatedProcedure
|
||||
.input(ZCreatePasskeyMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const verificationResponse = input.verificationResponse as RegistrationResponseJSON;
|
||||
|
||||
return await createPasskey({
|
||||
userId: ctx.user.id,
|
||||
verificationResponse,
|
||||
passkeyName: input.passkeyName,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
createPasskeyAuthenticationOptions: authenticatedProcedure
|
||||
.input(ZCreatePasskeyAuthenticationOptionsMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return await createPasskeyAuthenticationOptions({
|
||||
userId: ctx.user.id,
|
||||
preferredPasskeyId: input?.preferredPasskeyId,
|
||||
});
|
||||
}),
|
||||
|
||||
createPasskeyRegistrationOptions: authenticatedProcedure.mutation(async ({ ctx }) => {
|
||||
return await createPasskeyRegistrationOptions({
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
passkey: router({
|
||||
create: createPasskeyRoute,
|
||||
createAuthenticationOptions: createPasskeyAuthenticationOptionsRoute,
|
||||
createRegistrationOptions: createPasskeyRegistrationOptionsRoute,
|
||||
createSigninOptions: createPasskeySigninOptionsRoute,
|
||||
delete: deletePasskeyRoute,
|
||||
find: findPasskeysRoute,
|
||||
update: updatePasskeyRoute,
|
||||
}),
|
||||
|
||||
createPasskeySigninOptions: procedure.mutation(async () => {
|
||||
const sessionIdToken = nanoid(16);
|
||||
|
||||
const [sessionId] = decodeURI(sessionIdToken).split('|');
|
||||
|
||||
const options = await createPasskeySigninOptions({ sessionId });
|
||||
|
||||
return {
|
||||
options,
|
||||
sessionId,
|
||||
};
|
||||
}),
|
||||
|
||||
deletePasskey: authenticatedProcedure
|
||||
.input(ZDeletePasskeyMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { passkeyId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
passkeyId,
|
||||
},
|
||||
});
|
||||
|
||||
await deletePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
|
||||
findPasskeys: authenticatedProcedure
|
||||
.input(ZFindPasskeysQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { page, perPage, orderBy } = input;
|
||||
|
||||
return await findPasskeys({
|
||||
page,
|
||||
perPage,
|
||||
orderBy,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
updatePasskey: authenticatedProcedure
|
||||
.input(ZUpdatePasskeyMutationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { passkeyId, name } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
passkeyId,
|
||||
},
|
||||
});
|
||||
|
||||
await updatePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
name,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { ZRegistrationResponseJSONSchema } from '@documenso/lib/types/webauthn';
|
||||
|
||||
export const ZCurrentPasswordSchema = z
|
||||
.string()
|
||||
.min(6, { message: 'Must be at least 6 characters in length' })
|
||||
@ -24,50 +21,3 @@ export const ZPasswordSchema = z
|
||||
.refine((value) => value.length > 25 || /[`~<>?,./!@#$%^&*()\-_"'+=|{}[\];:\\]/.test(value), {
|
||||
message: 'One special character is required',
|
||||
});
|
||||
|
||||
export const ZSignUpMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
password: ZPasswordSchema,
|
||||
signature: z.string().nullish(),
|
||||
url: z
|
||||
.string()
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.min(1)
|
||||
.regex(/^[a-z0-9-]+$/, {
|
||||
message: 'Username can only container alphanumeric characters and dashes.',
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZCreatePasskeyMutationSchema = z.object({
|
||||
passkeyName: z.string().trim().min(1),
|
||||
verificationResponse: ZRegistrationResponseJSONSchema,
|
||||
});
|
||||
|
||||
export const ZCreatePasskeyAuthenticationOptionsMutationSchema = z
|
||||
.object({
|
||||
preferredPasskeyId: z.string().optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const ZDeletePasskeyMutationSchema = z.object({
|
||||
passkeyId: z.string().trim().min(1),
|
||||
});
|
||||
|
||||
export const ZUpdatePasskeyMutationSchema = z.object({
|
||||
passkeyId: z.string().trim().min(1),
|
||||
name: z.string().trim().min(1),
|
||||
});
|
||||
|
||||
export const ZFindPasskeysQuerySchema = ZFindSearchParamsSchema.extend({
|
||||
orderBy: z
|
||||
.object({
|
||||
column: z.enum(['createdAt', 'updatedAt', 'name']),
|
||||
direction: z.enum(['asc', 'desc']),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TSignUpMutationSchema = z.infer<typeof ZSignUpMutationSchema>;
|
||||
|
||||
24
packages/trpc/server/auth-router/update-passkey.ts
Normal file
24
packages/trpc/server/auth-router/update-passkey.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { updatePasskey } from '@documenso/lib/server-only/auth/update-passkey';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZUpdatePasskeyRequestSchema, ZUpdatePasskeyResponseSchema } from './update-passkey.types';
|
||||
|
||||
export const updatePasskeyRoute = authenticatedProcedure
|
||||
.input(ZUpdatePasskeyRequestSchema)
|
||||
.output(ZUpdatePasskeyResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { passkeyId, name } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
passkeyId,
|
||||
},
|
||||
});
|
||||
|
||||
await updatePasskey({
|
||||
userId: ctx.user.id,
|
||||
passkeyId,
|
||||
name,
|
||||
requestMetadata: ctx.metadata.requestMetadata,
|
||||
});
|
||||
});
|
||||
11
packages/trpc/server/auth-router/update-passkey.types.ts
Normal file
11
packages/trpc/server/auth-router/update-passkey.types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZUpdatePasskeyRequestSchema = z.object({
|
||||
passkeyId: z.string().trim().min(1),
|
||||
name: z.string().trim().min(1),
|
||||
});
|
||||
|
||||
export const ZUpdatePasskeyResponseSchema = z.void();
|
||||
|
||||
export type TUpdatePasskeyRequest = z.infer<typeof ZUpdatePasskeyRequestSchema>;
|
||||
export type TUpdatePasskeyResponse = z.infer<typeof ZUpdatePasskeyResponseSchema>;
|
||||
@ -0,0 +1,92 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { isValidExpirySettings } from '@documenso/lib/utils/expiry';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateDocumentTemporaryRequestSchema,
|
||||
ZCreateDocumentTemporaryResponseSchema,
|
||||
createDocumentTemporaryMeta,
|
||||
} from './create-document-temporary.types';
|
||||
|
||||
/**
|
||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||
*
|
||||
* @public
|
||||
* @deprecated
|
||||
*/
|
||||
export const createDocumentTemporaryRoute = authenticatedProcedure
|
||||
.meta(createDocumentTemporaryMeta)
|
||||
.input(ZCreateDocumentTemporaryRequestSchema)
|
||||
.output(ZCreateDocumentTemporaryResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
|
||||
const {
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
meta,
|
||||
folderId,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
} = input;
|
||||
|
||||
// Validate expiry settings
|
||||
if ((expiryAmount || expiryUnit) && !isValidExpirySettings(expiryAmount, expiryUnit)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Invalid expiry settings. Please check your expiry configuration.',
|
||||
});
|
||||
}
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
|
||||
const documentData = await createDocumentData({
|
||||
data: key,
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
const createdDocument = await createDocumentV2({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentDataId: documentData.id,
|
||||
normalizePdf: false, // Not normalizing because of presigned URL.
|
||||
data: {
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
folderId,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
document: createdDocument,
|
||||
uploadUrl: url,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,124 @@
|
||||
import { DocumentSigningOrder } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentSchema } from '@documenso/lib/types/document';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
|
||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import {
|
||||
ZDocumentExpiryAmountSchema,
|
||||
ZDocumentExpiryUnitSchema,
|
||||
ZDocumentExternalIdSchema,
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaDrawSignatureEnabledSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentMetaTypedSignatureEnabledSchema,
|
||||
ZDocumentMetaUploadSignatureEnabledSchema,
|
||||
ZDocumentTitleSchema,
|
||||
ZDocumentVisibilitySchema,
|
||||
} from './schema';
|
||||
|
||||
/**
|
||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||
*/
|
||||
export const createDocumentTemporaryMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/create/beta',
|
||||
summary: 'Create document',
|
||||
description:
|
||||
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZCreateDocumentTemporaryRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
visibility: ZDocumentVisibilitySchema.optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||
)
|
||||
.optional(),
|
||||
expiryAmount: ZDocumentExpiryAmountSchema.optional(),
|
||||
expiryUnit: ZDocumentExpiryUnitSchema.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{ message: 'Recipients must have unique emails' },
|
||||
)
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentTemporaryResponseSchema = z.object({
|
||||
document: ZDocumentSchema,
|
||||
uploadUrl: z
|
||||
.string()
|
||||
.describe(
|
||||
'The URL to upload the document PDF to. Use a PUT request with the file via form-data',
|
||||
),
|
||||
});
|
||||
|
||||
export type TCreateDocumentTemporaryRequest = z.infer<typeof ZCreateDocumentTemporaryRequestSchema>;
|
||||
export type TCreateDocumentTemporaryResponse = z.infer<
|
||||
typeof ZCreateDocumentTemporaryResponseSchema
|
||||
>;
|
||||
57
packages/trpc/server/document-router/create-document.ts
Normal file
57
packages/trpc/server/document-router/create-document.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
||||
import { isValidExpirySettings } from '@documenso/lib/utils/expiry';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateDocumentRequestSchema,
|
||||
ZCreateDocumentResponseSchema,
|
||||
} from './create-document.types';
|
||||
|
||||
export const createDocumentRoute = authenticatedProcedure
|
||||
.input(ZCreateDocumentRequestSchema)
|
||||
.output(ZCreateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { title, documentDataId, timezone, folderId, expiryAmount, expiryUnit } = input;
|
||||
|
||||
// Validate expiry settings
|
||||
if ((expiryAmount || expiryUnit) && !isValidExpirySettings(expiryAmount, expiryUnit)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Invalid expiry settings. Please check your expiry configuration.',
|
||||
});
|
||||
}
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const document = await createDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
normalizePdf: true,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
});
|
||||
|
||||
return {
|
||||
id: document.id,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,34 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
ZDocumentExpiryAmountSchema,
|
||||
ZDocumentExpiryUnitSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
ZDocumentTitleSchema,
|
||||
} from './schema';
|
||||
|
||||
// Currently not in use until we allow passthrough documents on create.
|
||||
// export const createDocumentMeta: TrpcRouteMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/document/create',
|
||||
// summary: 'Create document',
|
||||
// tags: ['Document'],
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZCreateDocumentRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string().min(1),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
|
||||
expiryAmount: ZDocumentExpiryAmountSchema.optional(),
|
||||
expiryUnit: ZDocumentExpiryUnitSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentResponseSchema = z.object({
|
||||
id: z.number(),
|
||||
});
|
||||
|
||||
export type TCreateDocumentRequest = z.infer<typeof ZCreateDocumentRequestSchema>;
|
||||
export type TCreateDocumentResponse = z.infer<typeof ZCreateDocumentResponseSchema>;
|
||||
35
packages/trpc/server/document-router/delete-document.ts
Normal file
35
packages/trpc/server/document-router/delete-document.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteDocumentRequestSchema,
|
||||
ZDeleteDocumentResponseSchema,
|
||||
deleteDocumentMeta,
|
||||
} from './delete-document.types';
|
||||
import { ZGenericSuccessResponse } from './schema';
|
||||
|
||||
export const deleteDocumentRoute = authenticatedProcedure
|
||||
.meta(deleteDocumentMeta)
|
||||
.input(ZDeleteDocumentRequestSchema)
|
||||
.output(ZDeleteDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
await deleteDocument({
|
||||
id: documentId,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZSuccessResponseSchema } from './schema';
|
||||
|
||||
export const deleteDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/delete',
|
||||
summary: 'Delete document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDeleteDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteDocumentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TDeleteDocumentRequest = z.infer<typeof ZDeleteDocumentRequestSchema>;
|
||||
export type TDeleteDocumentResponse = z.infer<typeof ZDeleteDocumentResponseSchema>;
|
||||
50
packages/trpc/server/document-router/distribute-document.ts
Normal file
50
packages/trpc/server/document-router/distribute-document.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDistributeDocumentRequestSchema,
|
||||
ZDistributeDocumentResponseSchema,
|
||||
distributeDocumentMeta,
|
||||
} from './distribute-document.types';
|
||||
|
||||
export const distributeDocumentRoute = authenticatedProcedure
|
||||
.meta(distributeDocumentMeta)
|
||||
.input(ZDistributeDocumentRequestSchema)
|
||||
.output(ZDistributeDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
language: meta.language,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,48 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentLiteSchema } from '@documenso/lib/types/document';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
ZDocumentMetaLanguageSchema,
|
||||
ZDocumentMetaMessageSchema,
|
||||
ZDocumentMetaRedirectUrlSchema,
|
||||
ZDocumentMetaSubjectSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from './schema';
|
||||
|
||||
export const distributeDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/distribute',
|
||||
summary: 'Distribute document',
|
||||
description: 'Send the document out to recipients based on your distribution method',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDistributeDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to send.'),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZDistributeDocumentResponseSchema = ZDocumentLiteSchema;
|
||||
|
||||
export type TDistributeDocumentRequest = z.infer<typeof ZDistributeDocumentRequestSchema>;
|
||||
export type TDistributeDocumentResponse = z.infer<typeof ZDistributeDocumentResponseSchema>;
|
||||
@ -0,0 +1,47 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDownloadDocumentAuditLogsRequestSchema,
|
||||
ZDownloadDocumentAuditLogsResponseSchema,
|
||||
} from './download-document-audit-logs.types';
|
||||
|
||||
export const downloadDocumentAuditLogsRoute = authenticatedProcedure
|
||||
.input(ZDownloadDocumentAuditLogsRequestSchema)
|
||||
.output(ZDownloadDocumentAuditLogsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDownloadDocumentAuditLogsRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentAuditLogsResponseSchema = z.object({
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentAuditLogsRequest = z.infer<
|
||||
typeof ZDownloadDocumentAuditLogsRequestSchema
|
||||
>;
|
||||
export type TDownloadDocumentAuditLogsResponse = z.infer<
|
||||
typeof ZDownloadDocumentAuditLogsResponseSchema
|
||||
>;
|
||||
@ -0,0 +1,46 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDownloadDocumentCertificateRequestSchema,
|
||||
ZDownloadDocumentCertificateResponseSchema,
|
||||
} from './download-document-certificate.types';
|
||||
|
||||
export const downloadDocumentCertificateRoute = authenticatedProcedure
|
||||
.input(ZDownloadDocumentCertificateRequestSchema)
|
||||
.output(ZDownloadDocumentCertificateResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentCompleted(document.status)) {
|
||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDownloadDocumentCertificateRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentCertificateResponseSchema = z.object({
|
||||
url: z.string(),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentCertificateRequest = z.infer<
|
||||
typeof ZDownloadDocumentCertificateRequestSchema
|
||||
>;
|
||||
export type TDownloadDocumentCertificateResponse = z.infer<
|
||||
typeof ZDownloadDocumentCertificateResponseSchema
|
||||
>;
|
||||
@ -6,18 +6,14 @@ import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZDownloadDocumentRequestSchema, ZDownloadDocumentResponseSchema } from './schema';
|
||||
import {
|
||||
ZDownloadDocumentRequestSchema,
|
||||
ZDownloadDocumentResponseSchema,
|
||||
downloadDocumentMeta,
|
||||
} from './download-document.types';
|
||||
|
||||
export const downloadDocumentRoute = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}/download-beta',
|
||||
summary: 'Download document (beta)',
|
||||
description: 'Get a pre-signed download URL for the original or signed version of a document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.meta(downloadDocumentMeta)
|
||||
.input(ZDownloadDocumentRequestSchema)
|
||||
.output(ZDownloadDocumentResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const downloadDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}/download-beta',
|
||||
summary: 'Download document (beta)',
|
||||
description: 'Get a pre-signed download URL for the original or signed version of a document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDownloadDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to download.'),
|
||||
version: z
|
||||
.enum(['original', 'signed'])
|
||||
.describe(
|
||||
'The version of the document to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
||||
)
|
||||
.default('signed'),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentResponseSchema = z.object({
|
||||
downloadUrl: z.string().describe('Pre-signed URL for downloading the PDF file'),
|
||||
filename: z.string().describe('The filename of the PDF file'),
|
||||
contentType: z.string().describe('MIME type of the file'),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentRequest = z.infer<typeof ZDownloadDocumentRequestSchema>;
|
||||
export type TDownloadDocumentResponse = z.infer<typeof ZDownloadDocumentResponseSchema>;
|
||||
29
packages/trpc/server/document-router/duplicate-document.ts
Normal file
29
packages/trpc/server/document-router/duplicate-document.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZDuplicateDocumentRequestSchema,
|
||||
ZDuplicateDocumentResponseSchema,
|
||||
duplicateDocumentMeta,
|
||||
} from './duplicate-document.types';
|
||||
|
||||
export const duplicateDocumentRoute = authenticatedProcedure
|
||||
.meta(duplicateDocumentMeta)
|
||||
.input(ZDuplicateDocumentRequestSchema)
|
||||
.output(ZDuplicateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const duplicateDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/duplicate',
|
||||
summary: 'Duplicate document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZDuplicateDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TDuplicateDocumentRequest = z.infer<typeof ZDuplicateDocumentRequestSchema>;
|
||||
export type TDuplicateDocumentResponse = z.infer<typeof ZDuplicateDocumentResponseSchema>;
|
||||
@ -0,0 +1,41 @@
|
||||
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentAuditLogsRequestSchema,
|
||||
ZFindDocumentAuditLogsResponseSchema,
|
||||
} from './find-document-audit-logs.types';
|
||||
|
||||
export const findDocumentAuditLogsRoute = authenticatedProcedure
|
||||
.input(ZFindDocumentAuditLogsRequestSchema)
|
||||
.output(ZFindDocumentAuditLogsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
const {
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderByColumn,
|
||||
orderByDirection,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findDocumentAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentAuditLogSchema } from '@documenso/lib/types/document-audit-logs';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const ZFindDocumentAuditLogsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
documentId: z.number().min(1),
|
||||
cursor: z.string().optional(),
|
||||
filterForRecentActivity: z.boolean().optional(),
|
||||
orderByColumn: z.enum(['createdAt', 'type']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
||||
});
|
||||
|
||||
export const ZFindDocumentAuditLogsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentAuditLogSchema.array(),
|
||||
nextCursor: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TFindDocumentAuditLogsRequest = z.infer<typeof ZFindDocumentAuditLogsRequestSchema>;
|
||||
export type TFindDocumentAuditLogsResponse = z.infer<typeof ZFindDocumentAuditLogsResponseSchema>;
|
||||
@ -0,0 +1,74 @@
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentsInternalRequestSchema,
|
||||
ZFindDocumentsInternalResponseSchema,
|
||||
} from './find-documents-internal.types';
|
||||
|
||||
export const findDocumentsInternalRoute = authenticatedProcedure
|
||||
.input(ZFindDocumentsInternalRequestSchema)
|
||||
.output(ZFindDocumentsInternalResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const getStatOptions: GetStatsInput = {
|
||||
user,
|
||||
period,
|
||||
search: query,
|
||||
folderId,
|
||||
};
|
||||
|
||||
if (teamId) {
|
||||
const team = await getTeamById({ userId: user.id, teamId });
|
||||
|
||||
getStatOptions.team = {
|
||||
teamId: team.id,
|
||||
teamEmail: team.teamEmail?.email,
|
||||
senderIds,
|
||||
currentTeamMemberRole: team.currentTeamRole,
|
||||
currentUserEmail: user.email,
|
||||
userId: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
const [stats, documents] = await Promise.all([
|
||||
getStats(getStatOptions),
|
||||
findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
...documents,
|
||||
stats,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||
import { ZFindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { ZFindDocumentsRequestSchema } from './find-documents.types';
|
||||
|
||||
export const ZFindDocumentsInternalRequestSchema = ZFindDocumentsRequestSchema.extend({
|
||||
period: z.enum(['7d', '14d', '30d']).optional(),
|
||||
senderIds: z.array(z.number()).optional(),
|
||||
status: z.nativeEnum(ExtendedDocumentStatus).optional(),
|
||||
folderId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsInternalResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
stats: z.object({
|
||||
[ExtendedDocumentStatus.DRAFT]: z.number(),
|
||||
[ExtendedDocumentStatus.PENDING]: z.number(),
|
||||
[ExtendedDocumentStatus.COMPLETED]: z.number(),
|
||||
[ExtendedDocumentStatus.REJECTED]: z.number(),
|
||||
[ExtendedDocumentStatus.INBOX]: z.number(),
|
||||
[ExtendedDocumentStatus.ALL]: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TFindDocumentsInternalRequest = z.infer<typeof ZFindDocumentsInternalRequestSchema>;
|
||||
export type TFindDocumentsInternalResponse = z.infer<typeof ZFindDocumentsInternalResponseSchema>;
|
||||
43
packages/trpc/server/document-router/find-documents.ts
Normal file
43
packages/trpc/server/document-router/find-documents.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentsMeta,
|
||||
ZFindDocumentsRequestSchema,
|
||||
ZFindDocumentsResponseSchema,
|
||||
} from './find-documents.types';
|
||||
|
||||
export const findDocumentsRoute = authenticatedProcedure
|
||||
.meta(ZFindDocumentsMeta)
|
||||
.input(ZFindDocumentsRequestSchema)
|
||||
.output(ZFindDocumentsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
query,
|
||||
source,
|
||||
status,
|
||||
page,
|
||||
perPage,
|
||||
folderId,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
|
||||
return documents;
|
||||
});
|
||||
42
packages/trpc/server/document-router/find-documents.types.ts
Normal file
42
packages/trpc/server/document-router/find-documents.types.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { DocumentSource, DocumentStatus } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const ZFindDocumentsMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document',
|
||||
summary: 'Find documents',
|
||||
description: 'Find documents based on a search criteria',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
templateId: z
|
||||
.number()
|
||||
.describe('Filter documents by the template ID used to create it.')
|
||||
.optional(),
|
||||
source: z
|
||||
.nativeEnum(DocumentSource)
|
||||
.describe('Filter documents by how it was created.')
|
||||
.optional(),
|
||||
status: z
|
||||
.nativeEnum(DocumentStatus)
|
||||
.describe('Filter documents by the current status')
|
||||
.optional(),
|
||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
});
|
||||
|
||||
export type TFindDocumentsRequest = z.infer<typeof ZFindDocumentsRequestSchema>;
|
||||
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
|
||||
@ -0,0 +1,43 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZGetDocumentByTokenRequestSchema,
|
||||
ZGetDocumentByTokenResponseSchema,
|
||||
} from './get-document-by-token.types';
|
||||
|
||||
export const getDocumentByTokenRoute = authenticatedProcedure
|
||||
.input(ZGetDocumentByTokenRequestSchema)
|
||||
.output(ZGetDocumentByTokenResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { token } = input;
|
||||
|
||||
const document = await prisma.document.findFirst({
|
||||
where: {
|
||||
recipients: {
|
||||
some: {
|
||||
token,
|
||||
email: ctx.user.email,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!document) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Document not found',
|
||||
});
|
||||
}
|
||||
|
||||
ctx.logger.info({
|
||||
documentId: document.id,
|
||||
});
|
||||
|
||||
return {
|
||||
documentData: document.documentData,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||
|
||||
export const ZGetDocumentByTokenRequestSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZGetDocumentByTokenResponseSchema = z.object({
|
||||
documentData: DocumentDataSchema,
|
||||
});
|
||||
|
||||
export type TGetDocumentByTokenRequest = z.infer<typeof ZGetDocumentByTokenRequestSchema>;
|
||||
export type TGetDocumentByTokenResponse = z.infer<typeof ZGetDocumentByTokenResponseSchema>;
|
||||
29
packages/trpc/server/document-router/get-document.ts
Normal file
29
packages/trpc/server/document-router/get-document.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZGetDocumentRequestSchema,
|
||||
ZGetDocumentResponseSchema,
|
||||
getDocumentMeta,
|
||||
} from './get-document.types';
|
||||
|
||||
export const getDocumentRoute = authenticatedProcedure
|
||||
.meta(getDocumentMeta)
|
||||
.input(ZGetDocumentRequestSchema)
|
||||
.output(ZGetDocumentResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentWithDetailsById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
});
|
||||
24
packages/trpc/server/document-router/get-document.types.ts
Normal file
24
packages/trpc/server/document-router/get-document.types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentSchema } from '@documenso/lib/types/document';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
|
||||
export const getDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}',
|
||||
summary: 'Get document',
|
||||
description: 'Returns a document given an ID',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZGetDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZGetDocumentResponseSchema = ZDocumentSchema;
|
||||
|
||||
export type TGetDocumentRequest = z.infer<typeof ZGetDocumentRequestSchema>;
|
||||
export type TGetDocumentResponse = z.infer<typeof ZGetDocumentResponseSchema>;
|
||||
@ -0,0 +1,35 @@
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZRedistributeDocumentRequestSchema,
|
||||
ZRedistributeDocumentResponseSchema,
|
||||
redistributeDocumentMeta,
|
||||
} from './redistribute-document.types';
|
||||
import { ZGenericSuccessResponse } from './schema';
|
||||
|
||||
export const redistributeDocumentRoute = authenticatedProcedure
|
||||
.meta(redistributeDocumentMeta)
|
||||
.input(ZRedistributeDocumentRequestSchema)
|
||||
.output(ZRedistributeDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
recipients,
|
||||
},
|
||||
});
|
||||
|
||||
await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
});
|
||||
@ -0,0 +1,28 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TrpcRouteMeta } from '../trpc';
|
||||
import { ZSuccessResponseSchema } from './schema';
|
||||
|
||||
export const redistributeDocumentMeta: TrpcRouteMeta = {
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/redistribute',
|
||||
summary: 'Redistribute document',
|
||||
description:
|
||||
'Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
};
|
||||
|
||||
export const ZRedistributeDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z
|
||||
.array(z.number())
|
||||
.min(1)
|
||||
.describe('The IDs of the recipients to redistribute the document to.'),
|
||||
});
|
||||
|
||||
export const ZRedistributeDocumentResponseSchema = ZSuccessResponseSchema;
|
||||
|
||||
export type TRedistributeDocumentRequest = z.infer<typeof ZRedistributeDocumentRequestSchema>;
|
||||
export type TRedistributeDocumentResponse = z.infer<typeof ZRedistributeDocumentResponseSchema>;
|
||||
@ -1,709 +1,49 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||
import { upsertDocumentMeta } from '@documenso/lib/server-only/document-meta/upsert-document-meta';
|
||||
import { createDocument } from '@documenso/lib/server-only/document/create-document';
|
||||
import { createDocumentV2 } from '@documenso/lib/server-only/document/create-document-v2';
|
||||
import { deleteDocument } from '@documenso/lib/server-only/document/delete-document';
|
||||
import { duplicateDocument } from '@documenso/lib/server-only/document/duplicate-document-by-id';
|
||||
import { findDocumentAuditLogs } from '@documenso/lib/server-only/document/find-document-audit-logs';
|
||||
import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
|
||||
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
|
||||
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { getStats } from '@documenso/lib/server-only/document/get-stats';
|
||||
import { resendDocument } from '@documenso/lib/server-only/document/resend-document';
|
||||
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||
import { sendDocument } from '@documenso/lib/server-only/document/send-document';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import { getPresignPostUrl } from '@documenso/lib/universal/upload/server-actions';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
import { isValidExpirySettings } from '@documenso/lib/utils/expiry';
|
||||
|
||||
import { authenticatedProcedure, procedure, router } from '../trpc';
|
||||
import { router } from '../trpc';
|
||||
import { createDocumentRoute } from './create-document';
|
||||
import { createDocumentTemporaryRoute } from './create-document-temporary';
|
||||
import { deleteDocumentRoute } from './delete-document';
|
||||
import { distributeDocumentRoute } from './distribute-document';
|
||||
import { downloadDocumentRoute } from './download-document';
|
||||
import { downloadDocumentAuditLogsRoute } from './download-document-audit-logs';
|
||||
import { downloadDocumentCertificateRoute } from './download-document-certificate';
|
||||
import { duplicateDocumentRoute } from './duplicate-document';
|
||||
import { findDocumentAuditLogsRoute } from './find-document-audit-logs';
|
||||
import { findDocumentsRoute } from './find-documents';
|
||||
import { findDocumentsInternalRoute } from './find-documents-internal';
|
||||
import { findInboxRoute } from './find-inbox';
|
||||
import { getDocumentRoute } from './get-document';
|
||||
import { getDocumentByTokenRoute } from './get-document-by-token';
|
||||
import { getInboxCountRoute } from './get-inbox-count';
|
||||
import {
|
||||
ZCreateDocumentRequestSchema,
|
||||
ZCreateDocumentV2RequestSchema,
|
||||
ZCreateDocumentV2ResponseSchema,
|
||||
ZDeleteDocumentMutationSchema,
|
||||
ZDistributeDocumentRequestSchema,
|
||||
ZDistributeDocumentResponseSchema,
|
||||
ZDownloadAuditLogsMutationSchema,
|
||||
ZDownloadCertificateMutationSchema,
|
||||
ZDuplicateDocumentRequestSchema,
|
||||
ZDuplicateDocumentResponseSchema,
|
||||
ZFindDocumentAuditLogsQuerySchema,
|
||||
ZFindDocumentsInternalRequestSchema,
|
||||
ZFindDocumentsInternalResponseSchema,
|
||||
ZFindDocumentsRequestSchema,
|
||||
ZFindDocumentsResponseSchema,
|
||||
ZGenericSuccessResponse,
|
||||
ZGetDocumentByIdQuerySchema,
|
||||
ZGetDocumentByTokenQuerySchema,
|
||||
ZGetDocumentWithDetailsByIdRequestSchema,
|
||||
ZGetDocumentWithDetailsByIdResponseSchema,
|
||||
ZResendDocumentMutationSchema,
|
||||
ZSearchDocumentsMutationSchema,
|
||||
ZSetSigningOrderForDocumentMutationSchema,
|
||||
ZSuccessResponseSchema,
|
||||
} from './schema';
|
||||
import { redistributeDocumentRoute } from './redistribute-document';
|
||||
import { searchDocumentRoute } from './search-document';
|
||||
import { updateDocumentRoute } from './update-document';
|
||||
|
||||
export const documentRouter = router({
|
||||
inbox: {
|
||||
get: getDocumentRoute,
|
||||
find: findDocumentsRoute,
|
||||
create: createDocumentRoute,
|
||||
update: updateDocumentRoute,
|
||||
delete: deleteDocumentRoute,
|
||||
duplicate: duplicateDocumentRoute,
|
||||
downloadCertificate: downloadDocumentCertificateRoute,
|
||||
distribute: distributeDocumentRoute,
|
||||
redistribute: redistributeDocumentRoute,
|
||||
search: searchDocumentRoute,
|
||||
|
||||
// Temporary v2 beta routes to be removed once V2 is fully released.
|
||||
download: downloadDocumentRoute,
|
||||
createDocumentTemporary: createDocumentTemporaryRoute,
|
||||
|
||||
// Internal document routes for custom frontend requests.
|
||||
getDocumentByToken: getDocumentByTokenRoute,
|
||||
findDocumentsInternal: findDocumentsInternalRoute,
|
||||
|
||||
auditLog: {
|
||||
find: findDocumentAuditLogsRoute,
|
||||
download: downloadDocumentAuditLogsRoute,
|
||||
},
|
||||
inbox: router({
|
||||
find: findInboxRoute,
|
||||
getCount: getInboxCountRoute,
|
||||
},
|
||||
updateDocument: updateDocumentRoute,
|
||||
downloadDocument: downloadDocumentRoute,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getDocumentById: authenticatedProcedure
|
||||
.input(ZGetDocumentByIdQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentById({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getDocumentByToken: procedure
|
||||
.input(ZGetDocumentByTokenQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { token } = input;
|
||||
|
||||
return await getDocumentAndSenderByToken({
|
||||
token,
|
||||
userId: ctx.user?.id,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
findDocuments: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document',
|
||||
summary: 'Find documents',
|
||||
description: 'Find documents based on a search criteria',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZFindDocumentsRequestSchema)
|
||||
.output(ZFindDocumentsResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const documents = await findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
templateId,
|
||||
query,
|
||||
source,
|
||||
status,
|
||||
page,
|
||||
perPage,
|
||||
folderId,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
|
||||
return documents;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Internal endpoint for /documents page to additionally return getStats.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
findDocumentsInternal: authenticatedProcedure
|
||||
.input(ZFindDocumentsInternalRequestSchema)
|
||||
.output(ZFindDocumentsInternalResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
|
||||
const {
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
orderByDirection,
|
||||
orderByColumn,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
const getStatOptions: GetStatsInput = {
|
||||
user,
|
||||
period,
|
||||
search: query,
|
||||
folderId,
|
||||
};
|
||||
|
||||
if (teamId) {
|
||||
const team = await getTeamById({ userId: user.id, teamId });
|
||||
|
||||
getStatOptions.team = {
|
||||
teamId: team.id,
|
||||
teamEmail: team.teamEmail?.email,
|
||||
senderIds,
|
||||
currentTeamMemberRole: team.currentTeamRole,
|
||||
currentUserEmail: user.email,
|
||||
userId: user.id,
|
||||
};
|
||||
}
|
||||
|
||||
const [stats, documents] = await Promise.all([
|
||||
getStats(getStatOptions),
|
||||
findDocuments({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
query,
|
||||
templateId,
|
||||
page,
|
||||
perPage,
|
||||
source,
|
||||
status,
|
||||
period,
|
||||
senderIds,
|
||||
folderId,
|
||||
orderBy: orderByColumn
|
||||
? { column: orderByColumn, direction: orderByDirection }
|
||||
: undefined,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
...documents,
|
||||
stats,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to getDocumentById.
|
||||
*/
|
||||
getDocumentWithDetailsById: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/document/{documentId}',
|
||||
summary: 'Get document',
|
||||
description: 'Returns a document given an ID',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZGetDocumentWithDetailsByIdRequestSchema)
|
||||
.output(ZGetDocumentWithDetailsByIdResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId, folderId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
return await getDocumentWithDetailsById({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
folderId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Temporariy endpoint for V2 Beta until we allow passthrough documents on create.
|
||||
*
|
||||
* @public
|
||||
* @deprecated
|
||||
*/
|
||||
createDocumentTemporary: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/create/beta',
|
||||
summary: 'Create document',
|
||||
description:
|
||||
'You will need to upload the PDF to the provided URL returned. Note: Once V2 API is released, this will be removed since we will allow direct uploads, instead of using an upload URL.',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZCreateDocumentV2RequestSchema)
|
||||
.output(ZCreateDocumentV2ResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
|
||||
const {
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
meta,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
} = input;
|
||||
|
||||
if ((expiryAmount || expiryUnit) && !isValidExpirySettings(expiryAmount, expiryUnit)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Invalid expiry settings. Please check your expiry configuration.',
|
||||
});
|
||||
}
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const fileName = title.endsWith('.pdf') ? title : `${title}.pdf`;
|
||||
|
||||
const { url, key } = await getPresignPostUrl(fileName, 'application/pdf');
|
||||
|
||||
const documentData = await createDocumentData({
|
||||
data: key,
|
||||
type: DocumentDataType.S3_PATH,
|
||||
});
|
||||
|
||||
const createdDocument = await createDocumentV2({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentDataId: documentData.id,
|
||||
normalizePdf: false, // Not normalizing because of presigned URL.
|
||||
data: {
|
||||
title,
|
||||
externalId,
|
||||
visibility,
|
||||
globalAccessAuth,
|
||||
globalActionAuth,
|
||||
recipients,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
},
|
||||
meta,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return {
|
||||
document: createdDocument,
|
||||
folder: createdDocument.folder, // Todo: Remove this prior to api-v2 release.
|
||||
uploadUrl: url,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* Wait until RR7 so we can passthrough documents.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createDocument: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/document/create',
|
||||
// summary: 'Create document',
|
||||
// tags: ['Document'],
|
||||
// },
|
||||
// })
|
||||
.input(ZCreateDocumentRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { user, teamId } = ctx;
|
||||
const { title, documentDataId, timezone, folderId, expiryAmount, expiryUnit } = input;
|
||||
|
||||
// Validate expiry settings
|
||||
if ((expiryAmount || expiryUnit) && !isValidExpirySettings(expiryAmount, expiryUnit)) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Invalid expiry settings. Please check your expiry configuration.',
|
||||
});
|
||||
}
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
folderId,
|
||||
},
|
||||
});
|
||||
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached your document limit for this month. Please upgrade your plan.',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
return await createDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
normalizePdf: true,
|
||||
userTimezone: timezone,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
expiryAmount,
|
||||
expiryUnit,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
deleteDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/delete',
|
||||
summary: 'Delete document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDeleteDocumentMutationSchema)
|
||||
.output(ZSuccessResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
await deleteDocument({
|
||||
id: documentId,
|
||||
userId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Todo: Remove and use `updateDocument` endpoint instead.
|
||||
*/
|
||||
setSigningOrderForDocument: authenticatedProcedure
|
||||
.input(ZSetSigningOrderForDocumentMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, signingOrder } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
signingOrder,
|
||||
},
|
||||
});
|
||||
|
||||
return await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
signingOrder,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to distributeDocument.
|
||||
* Todo: Rework before releasing API.
|
||||
*/
|
||||
sendDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/distribute',
|
||||
summary: 'Distribute document',
|
||||
description: 'Send the document out to recipients based on your distribution method',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDistributeDocumentRequestSchema)
|
||||
.output(ZDistributeDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, meta = {} } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.values(meta).length > 0) {
|
||||
await upsertDocumentMeta({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
subject: meta.subject,
|
||||
message: meta.message,
|
||||
dateFormat: meta.dateFormat,
|
||||
timezone: meta.timezone,
|
||||
redirectUrl: meta.redirectUrl,
|
||||
distributionMethod: meta.distributionMethod,
|
||||
emailSettings: meta.emailSettings,
|
||||
language: meta.language,
|
||||
emailId: meta.emailId,
|
||||
emailReplyTo: meta.emailReplyTo,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}
|
||||
|
||||
return await sendDocument({
|
||||
userId: ctx.user.id,
|
||||
documentId,
|
||||
teamId,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Todo: Refactor to redistributeDocument.
|
||||
*/
|
||||
resendDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/redistribute',
|
||||
summary: 'Redistribute document',
|
||||
description:
|
||||
'Redistribute the document to the provided recipients who have not actioned the document. Will use the distribution method set in the document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZResendDocumentMutationSchema)
|
||||
.output(ZSuccessResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId, recipients } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
recipients,
|
||||
},
|
||||
});
|
||||
|
||||
await resendDocument({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
recipients,
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
|
||||
return ZGenericSuccessResponse;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
duplicateDocument: authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'POST',
|
||||
path: '/document/duplicate',
|
||||
summary: 'Duplicate document',
|
||||
tags: ['Document'],
|
||||
},
|
||||
})
|
||||
.input(ZDuplicateDocumentRequestSchema)
|
||||
.output(ZDuplicateDocumentResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId, user } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await duplicateDocument({
|
||||
userId: user.id,
|
||||
teamId,
|
||||
documentId,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
searchDocuments: authenticatedProcedure
|
||||
.input(ZSearchDocumentsMutationSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { query } = input;
|
||||
|
||||
const documents = await searchDocumentsWithKeyword({
|
||||
query,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
|
||||
return documents;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
findDocumentAuditLogs: authenticatedProcedure
|
||||
.input(ZFindDocumentAuditLogsQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
|
||||
const {
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderByColumn,
|
||||
orderByDirection,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return await findDocumentAuditLogs({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
page,
|
||||
perPage,
|
||||
documentId,
|
||||
cursor,
|
||||
filterForRecentActivity,
|
||||
orderBy: orderByColumn ? { column: orderByColumn, direction: orderByDirection } : undefined,
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
downloadAuditLogs: authenticatedProcedure
|
||||
.input(ZDownloadAuditLogsMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
}).catch(() => null);
|
||||
|
||||
if (!document || (teamId && document.teamId !== teamId)) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have access to this document.',
|
||||
});
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/audit-log?d=${encrypted}`,
|
||||
};
|
||||
}),
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
downloadCertificate: authenticatedProcedure
|
||||
.input(ZDownloadCertificateMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { documentId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
documentId,
|
||||
},
|
||||
});
|
||||
|
||||
const document = await getDocumentById({
|
||||
documentId,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
});
|
||||
|
||||
if (!isDocumentCompleted(document.status)) {
|
||||
throw new AppError('DOCUMENT_NOT_COMPLETE');
|
||||
}
|
||||
|
||||
const encrypted = encryptSecondaryData({
|
||||
data: document.id.toString(),
|
||||
expiresAt: DateTime.now().plus({ minutes: 5 }).toJSDate().valueOf(),
|
||||
});
|
||||
|
||||
return {
|
||||
url: `${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encrypted}`,
|
||||
};
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -1,39 +1,9 @@
|
||||
import {
|
||||
DocumentDistributionMethod,
|
||||
DocumentSigningOrder,
|
||||
DocumentSource,
|
||||
DocumentStatus,
|
||||
DocumentVisibility,
|
||||
FieldType,
|
||||
} from '@prisma/client';
|
||||
import { DocumentDistributionMethod, DocumentVisibility } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { VALID_DATE_FORMAT_VALUES } from '@documenso/lib/constants/date-formats';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import {
|
||||
ZDocumentLiteSchema,
|
||||
ZDocumentManySchema,
|
||||
ZDocumentSchema,
|
||||
} from '@documenso/lib/types/document';
|
||||
import {
|
||||
ZDocumentAccessAuthTypesSchema,
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZFieldHeightSchema,
|
||||
ZFieldPageNumberSchema,
|
||||
ZFieldPageXSchema,
|
||||
ZFieldPageYSchema,
|
||||
ZFieldWidthSchema,
|
||||
} from '@documenso/lib/types/field';
|
||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
|
||||
import { ZCreateRecipientSchema } from '../recipient-router/schema';
|
||||
|
||||
/**
|
||||
* Required for empty responses since we currently can't 201 requests for our openapi setup.
|
||||
@ -126,256 +96,3 @@ export const ZDocumentExpiryAmountSchema = z
|
||||
export const ZDocumentExpiryUnitSchema = z
|
||||
.enum(['minutes', 'hours', 'days', 'weeks', 'months'])
|
||||
.describe('The unit for expiry duration (e.g., "days" for "3 days").');
|
||||
|
||||
export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
templateId: z
|
||||
.number()
|
||||
.describe('Filter documents by the template ID used to create it.')
|
||||
.optional(),
|
||||
source: z
|
||||
.nativeEnum(DocumentSource)
|
||||
.describe('Filter documents by how it was created.')
|
||||
.optional(),
|
||||
status: z
|
||||
.nativeEnum(DocumentStatus)
|
||||
.describe('Filter documents by the current status')
|
||||
.optional(),
|
||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).describe('').default('desc'),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
});
|
||||
|
||||
export type TFindDocumentsResponse = z.infer<typeof ZFindDocumentsResponseSchema>;
|
||||
|
||||
export const ZFindDocumentsInternalRequestSchema = ZFindDocumentsRequestSchema.extend({
|
||||
period: z.enum(['7d', '14d', '30d']).optional(),
|
||||
senderIds: z.array(z.number()).optional(),
|
||||
status: z.nativeEnum(ExtendedDocumentStatus).optional(),
|
||||
folderId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZFindDocumentsInternalResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentManySchema.array(),
|
||||
stats: z.object({
|
||||
[ExtendedDocumentStatus.DRAFT]: z.number(),
|
||||
[ExtendedDocumentStatus.PENDING]: z.number(),
|
||||
[ExtendedDocumentStatus.COMPLETED]: z.number(),
|
||||
[ExtendedDocumentStatus.REJECTED]: z.number(),
|
||||
[ExtendedDocumentStatus.INBOX]: z.number(),
|
||||
[ExtendedDocumentStatus.ALL]: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type TFindDocumentsInternalResponse = z.infer<typeof ZFindDocumentsInternalResponseSchema>;
|
||||
|
||||
export const ZFindDocumentAuditLogsQuerySchema = ZFindSearchParamsSchema.extend({
|
||||
documentId: z.number().min(1),
|
||||
cursor: z.string().optional(),
|
||||
filterForRecentActivity: z.boolean().optional(),
|
||||
orderByColumn: z.enum(['createdAt', 'type']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).default('desc'),
|
||||
});
|
||||
|
||||
export const ZGetDocumentByIdQuerySchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDuplicateDocumentResponseSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZGetDocumentByTokenQuerySchema = z.object({
|
||||
token: z.string().min(1),
|
||||
});
|
||||
|
||||
export type TGetDocumentByTokenQuerySchema = z.infer<typeof ZGetDocumentByTokenQuerySchema>;
|
||||
|
||||
export const ZGetDocumentWithDetailsByIdRequestSchema = z.object({
|
||||
documentId: z.number(),
|
||||
folderId: z.string().describe('Filter documents by folder ID').optional(),
|
||||
});
|
||||
|
||||
export const ZGetDocumentWithDetailsByIdResponseSchema = ZDocumentSchema;
|
||||
|
||||
export const ZCreateDocumentRequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
documentDataId: z.string().min(1),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
folderId: z.string().describe('The ID of the folder to create the document in').optional(),
|
||||
expiryAmount: ZDocumentExpiryAmountSchema.optional(),
|
||||
expiryUnit: ZDocumentExpiryUnitSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentV2RequestSchema = z.object({
|
||||
title: ZDocumentTitleSchema,
|
||||
externalId: ZDocumentExternalIdSchema.optional(),
|
||||
visibility: ZDocumentVisibilitySchema.optional(),
|
||||
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional(),
|
||||
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional(),
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
expiryAmount: ZDocumentExpiryAmountSchema.optional(),
|
||||
expiryUnit: ZDocumentExpiryUnitSchema.optional(),
|
||||
recipients: z
|
||||
.array(
|
||||
ZCreateRecipientSchema.extend({
|
||||
fields: ZFieldAndMetaSchema.and(
|
||||
z.object({
|
||||
pageNumber: ZFieldPageNumberSchema,
|
||||
pageX: ZFieldPageXSchema,
|
||||
pageY: ZFieldPageYSchema,
|
||||
width: ZFieldWidthSchema,
|
||||
height: ZFieldHeightSchema,
|
||||
}),
|
||||
)
|
||||
.array()
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.refine(
|
||||
(recipients) => {
|
||||
const emails = recipients.map((recipient) => recipient.email);
|
||||
|
||||
return new Set(emails).size === emails.length;
|
||||
},
|
||||
{ message: 'Recipients must have unique emails' },
|
||||
)
|
||||
.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder).optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
typedSignatureEnabled: ZDocumentMetaTypedSignatureEnabledSchema.optional(),
|
||||
drawSignatureEnabled: ZDocumentMetaDrawSignatureEnabledSchema.optional(),
|
||||
uploadSignatureEnabled: ZDocumentMetaUploadSignatureEnabledSchema.optional(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TCreateDocumentV2Request = z.infer<typeof ZCreateDocumentV2RequestSchema>;
|
||||
|
||||
export const ZCreateDocumentV2ResponseSchema = z.object({
|
||||
document: ZDocumentSchema,
|
||||
uploadUrl: z
|
||||
.string()
|
||||
.describe(
|
||||
'The URL to upload the document PDF to. Use a PUT request with the file via form-data',
|
||||
),
|
||||
});
|
||||
|
||||
export const ZSetFieldsForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
fields: z.array(
|
||||
z.object({
|
||||
id: z.number().nullish(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
signerEmail: z.string().min(1),
|
||||
pageNumber: z.number().min(1),
|
||||
pageX: z.number().min(0),
|
||||
pageY: z.number().min(0),
|
||||
pageWidth: z.number().min(0),
|
||||
pageHeight: z.number().min(0),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
export type TSetFieldsForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetFieldsForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZDistributeDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to send.'),
|
||||
meta: z
|
||||
.object({
|
||||
subject: ZDocumentMetaSubjectSchema.optional(),
|
||||
message: ZDocumentMetaMessageSchema.optional(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.optional(),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.optional(),
|
||||
distributionMethod: ZDocumentMetaDistributionMethodSchema.optional(),
|
||||
redirectUrl: ZDocumentMetaRedirectUrlSchema.optional(),
|
||||
language: ZDocumentMetaLanguageSchema.optional(),
|
||||
emailId: z.string().nullish(),
|
||||
emailReplyTo: z.string().email().nullish(),
|
||||
emailSettings: ZDocumentEmailSettingsSchema.optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZDistributeDocumentResponseSchema = ZDocumentLiteSchema;
|
||||
|
||||
export const ZSetPasswordForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
password: z.string(),
|
||||
});
|
||||
|
||||
export type TSetPasswordForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetPasswordForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZSetSigningOrderForDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
signingOrder: z.nativeEnum(DocumentSigningOrder),
|
||||
});
|
||||
|
||||
export type TSetSigningOrderForDocumentMutationSchema = z.infer<
|
||||
typeof ZSetSigningOrderForDocumentMutationSchema
|
||||
>;
|
||||
|
||||
export const ZResendDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
recipients: z
|
||||
.array(z.number())
|
||||
.min(1)
|
||||
.describe('The IDs of the recipients to redistribute the document to.'),
|
||||
});
|
||||
|
||||
export const ZDeleteDocumentMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export type TDeleteDocumentMutationSchema = z.infer<typeof ZDeleteDocumentMutationSchema>;
|
||||
|
||||
export const ZSearchDocumentsMutationSchema = z.object({
|
||||
query: z.string(),
|
||||
});
|
||||
|
||||
export const ZDownloadAuditLogsMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadCertificateMutationSchema = z.object({
|
||||
documentId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentRequestSchema = z.object({
|
||||
documentId: z.number().describe('The ID of the document to download.'),
|
||||
version: z
|
||||
.enum(['original', 'signed'])
|
||||
.describe(
|
||||
'The version of the document to download. "signed" returns the completed document with signatures, "original" returns the original uploaded document.',
|
||||
)
|
||||
.default('signed'),
|
||||
});
|
||||
|
||||
export const ZDownloadDocumentResponseSchema = z.object({
|
||||
downloadUrl: z.string().describe('Pre-signed URL for downloading the PDF file'),
|
||||
filename: z.string().describe('The filename of the PDF file'),
|
||||
contentType: z.string().describe('MIME type of the file'),
|
||||
});
|
||||
|
||||
export type TDownloadDocumentRequest = z.infer<typeof ZDownloadDocumentRequestSchema>;
|
||||
export type TDownloadDocumentResponse = z.infer<typeof ZDownloadDocumentResponseSchema>;
|
||||
|
||||
21
packages/trpc/server/document-router/search-document.ts
Normal file
21
packages/trpc/server/document-router/search-document.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { searchDocumentsWithKeyword } from '@documenso/lib/server-only/document/search-documents-with-keyword';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZSearchDocumentRequestSchema,
|
||||
ZSearchDocumentResponseSchema,
|
||||
} from './search-document.types';
|
||||
|
||||
export const searchDocumentRoute = authenticatedProcedure
|
||||
.input(ZSearchDocumentRequestSchema)
|
||||
.output(ZSearchDocumentResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { query } = input;
|
||||
|
||||
const documents = await searchDocumentsWithKeyword({
|
||||
query,
|
||||
userId: ctx.user.id,
|
||||
});
|
||||
|
||||
return documents;
|
||||
});
|
||||
@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZSearchDocumentRequestSchema = z.object({
|
||||
query: z.string(),
|
||||
});
|
||||
|
||||
export const ZSearchDocumentResponseSchema = z
|
||||
.object({
|
||||
title: z.string(),
|
||||
path: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
.array();
|
||||
|
||||
export type TSearchDocumentRequest = z.infer<typeof ZSearchDocumentRequestSchema>;
|
||||
export type TSearchDocumentResponse = z.infer<typeof ZSearchDocumentResponseSchema>;
|
||||
@ -40,7 +40,13 @@ export const createOrganisationGroupRoute = authenticatedProcedure
|
||||
groups: true,
|
||||
members: {
|
||||
include: {
|
||||
user: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@ -38,7 +39,7 @@ export const updateOrganisationRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.organisation.update({
|
||||
const updatedOrganisation = await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
@ -47,4 +48,12 @@ export const updateOrganisationRoute = authenticatedProcedure
|
||||
url: data.url,
|
||||
},
|
||||
});
|
||||
|
||||
if (updatedOrganisation.customerId) {
|
||||
await stripe.customers.update(updatedOrganisation.customerId, {
|
||||
metadata: {
|
||||
organisationName: data.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { SetAvatarImageOptions } 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 { 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 { updateProfile } from '@documenso/lib/server-only/user/update-profile';
|
||||
|
||||
import { adminProcedure, authenticatedProcedure, router } from '../trpc';
|
||||
import { authenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
ZFindUserSecurityAuditLogsSchema,
|
||||
ZRetrieveUserByIdQuerySchema,
|
||||
ZSetProfileImageMutationSchema,
|
||||
ZSubmitSupportTicketMutationSchema,
|
||||
ZUpdateProfileMutationSchema,
|
||||
} from './schema';
|
||||
|
||||
@ -23,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
|
||||
.input(ZUpdateProfileMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { name, signature } = input;
|
||||
|
||||
return await updateProfile({
|
||||
await updateProfile({
|
||||
userId: ctx.user.id,
|
||||
name,
|
||||
signature,
|
||||
@ -49,7 +38,7 @@ export const profileRouter = router({
|
||||
}),
|
||||
|
||||
deleteAccount: authenticatedProcedure.mutation(async ({ ctx }) => {
|
||||
return await deleteUser({
|
||||
await deleteUser({
|
||||
id: ctx.user.id,
|
||||
});
|
||||
}),
|
||||
@ -91,4 +80,28 @@ export const profileRouter = router({
|
||||
requestMetadata: ctx.metadata,
|
||||
});
|
||||
}),
|
||||
|
||||
submitSupportTicket: authenticatedProcedure
|
||||
.input(ZSubmitSupportTicketMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { subject, message, organisationId, teamId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const parsedTeamId = teamId ? Number(teamId) : null;
|
||||
|
||||
if (Number.isNaN(parsedTeamId)) {
|
||||
throw new AppError(AppErrorCode.INVALID_BODY, {
|
||||
message: 'Invalid team ID provided',
|
||||
});
|
||||
}
|
||||
|
||||
return await submitSupportTicket({
|
||||
subject,
|
||||
message,
|
||||
userId,
|
||||
organisationId,
|
||||
teamId: parsedTeamId,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -7,12 +7,6 @@ export const ZFindUserSecurityAuditLogsSchema = z.object({
|
||||
|
||||
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({
|
||||
name: z.string().min(1),
|
||||
signature: z.string(),
|
||||
@ -27,3 +21,12 @@ export const ZSetProfileImageMutationSchema = z.object({
|
||||
});
|
||||
|
||||
export type TSetProfileImageMutationSchema = z.infer<typeof ZSetProfileImageMutationSchema>;
|
||||
|
||||
export const ZSubmitSupportTicketMutationSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
teamId: z.string().min(1).nullish(),
|
||||
subject: z.string().min(3, 'Subject is required'),
|
||||
message: z.string().min(10, 'Message must be at least 10 characters'),
|
||||
});
|
||||
|
||||
export type TSupportTicketRequest = z.infer<typeof ZSubmitSupportTicketMutationSchema>;
|
||||
|
||||
@ -339,8 +339,14 @@ export const templateRouter = router({
|
||||
.output(ZCreateDocumentFromTemplateResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { teamId } = ctx;
|
||||
const { templateId, recipients, distributeDocument, customDocumentDataId, prefillFields } =
|
||||
input;
|
||||
const {
|
||||
templateId,
|
||||
recipients,
|
||||
distributeDocument,
|
||||
customDocumentDataId,
|
||||
prefillFields,
|
||||
folderId,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@ -361,6 +367,7 @@ export const templateRouter = router({
|
||||
recipients,
|
||||
customDocumentDataId,
|
||||
requestMetadata: ctx.metadata,
|
||||
folderId,
|
||||
prefillFields,
|
||||
});
|
||||
|
||||
|
||||
@ -117,6 +117,12 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
|
||||
'The data ID of an alternative PDF to use when creating the document. If not provided, the PDF attached to the template will be used.',
|
||||
)
|
||||
.optional(),
|
||||
folderId: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the folder to create the document in. If not provided, the document will be created in the root folder.',
|
||||
)
|
||||
.optional(),
|
||||
prefillFields: z
|
||||
.array(ZFieldMetaPrefillFieldsSchema)
|
||||
.describe(
|
||||
|
||||
Reference in New Issue
Block a user