From a48bda0d273acf22a81d7000a79d7e11d363797e Mon Sep 17 00:00:00 2001
From: Ephraim Duncan <55143799+dephraiim@users.noreply.github.com>
Date: Thu, 22 Feb 2024 05:36:34 +0000
Subject: [PATCH 1/3] feat: add page numbers to documents (#946)
---
packages/ui/primitives/pdf-viewer.tsx | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/packages/ui/primitives/pdf-viewer.tsx b/packages/ui/primitives/pdf-viewer.tsx
index b4e5c10ba..1069290e6 100644
--- a/packages/ui/primitives/pdf-viewer.tsx
+++ b/packages/ui/primitives/pdf-viewer.tsx
@@ -233,18 +233,20 @@ export const PDFViewer = ({
{Array(numPages)
.fill(null)
.map((_, i) => (
-
-
''}
- onClick={(e) => onDocumentPageClick(e, i + 1)}
- />
+
+
+
''}
+ onClick={(e) => onDocumentPageClick(e, i + 1)}
+ />
+
+
+ Page {i + 1} of {numPages}
+
))}
From dd29845934985a046ec1ab8255d604ef2e2deef8 Mon Sep 17 00:00:00 2001
From: Sumit Bisht <75713174+sumitbishti@users.noreply.github.com>
Date: Thu, 22 Feb 2024 11:19:54 +0530
Subject: [PATCH 2/3] fix: put max height in teams section in profile dropdown
(#947)
fixes: #942
---
.../(dashboard)/layout/menu-switcher.tsx | 34 ++++++++++---------
1 file changed, 18 insertions(+), 16 deletions(-)
diff --git a/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx b/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx
index 195716d64..765343d27 100644
--- a/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx
+++ b/apps/web/src/components/(dashboard)/layout/menu-switcher.tsx
@@ -166,22 +166,24 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
- {teams.map((team) => (
-
-
-
- )
- }
- />
-
-
- ))}
+
+ {teams.map((team) => (
+
+
+
+ )
+ }
+ />
+
+
+ ))}
+
>
) : (
From 306e5ff31f01cd2b168550e159b84e505163ac34 Mon Sep 17 00:00:00 2001
From: Lucas Smith
Date: Thu, 22 Feb 2024 11:05:49 +0000
Subject: [PATCH 3/3] fix: add timeouts to longer transactions
---
.../server-only/document/resend-document.tsx | 72 +++++-----
.../document/send-completed-email.ts | 75 +++++-----
.../server-only/document/send-document.tsx | 87 +++++------
.../lib/server-only/document/update-title.ts | 44 +++---
.../team/accept-team-invitation.ts | 85 +++++------
.../team/create-team-email-verification.ts | 85 +++++------
.../server-only/team/delete-team-members.ts | 135 +++++++++---------
packages/lib/server-only/team/delete-team.ts | 59 ++++----
packages/lib/server-only/team/leave-team.ts | 69 ++++-----
.../team/request-team-ownership-transfer.ts | 105 +++++++-------
.../team/resend-team-email-verification.ts | 75 +++++-----
.../team/resend-team-member-invitation.ts | 65 +++++----
.../team/transfer-team-ownership.ts | 133 ++++++++---------
packages/lib/server-only/user/create-user.ts | 81 ++++++-----
14 files changed, 607 insertions(+), 563 deletions(-)
diff --git a/packages/lib/server-only/document/resend-document.tsx b/packages/lib/server-only/document/resend-document.tsx
index 0dbda803e..ebf140007 100644
--- a/packages/lib/server-only/document/resend-document.tsx
+++ b/packages/lib/server-only/document/resend-document.tsx
@@ -16,9 +16,8 @@ import { prisma } from '@documenso/prisma';
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
import type { Prisma } from '@documenso/prisma/client';
-import { getDocumentWhereInput } from './get-document-by-id';
-
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
+import { getDocumentWhereInput } from './get-document-by-id';
export type ResendDocumentOptions = {
documentId: number;
@@ -111,40 +110,43 @@ export const resendDocument = async ({
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
- await prisma.$transaction(async (tx) => {
- await mailer.sendMail({
- to: {
- address: email,
- name,
- },
- from: {
- name: FROM_NAME,
- address: FROM_ADDRESS,
- },
- subject: customEmail?.subject
- ? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate)
- : `Please ${actionVerb.toLowerCase()} this document`,
- html: render(template),
- text: render(template, { plainText: true }),
- });
-
- await tx.documentAuditLog.create({
- data: createDocumentAuditLogData({
- type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
- documentId: document.id,
- user,
- requestMetadata,
- data: {
- emailType: recipientEmailType,
- recipientEmail: recipient.email,
- recipientName: recipient.name,
- recipientRole: recipient.role,
- recipientId: recipient.id,
- isResending: true,
+ await prisma.$transaction(
+ async (tx) => {
+ await mailer.sendMail({
+ to: {
+ address: email,
+ name,
},
- }),
- });
- });
+ from: {
+ name: FROM_NAME,
+ address: FROM_ADDRESS,
+ },
+ subject: customEmail?.subject
+ ? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate)
+ : `Please ${actionVerb.toLowerCase()} this document`,
+ html: render(template),
+ text: render(template, { plainText: true }),
+ });
+
+ await tx.documentAuditLog.create({
+ data: createDocumentAuditLogData({
+ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
+ documentId: document.id,
+ user,
+ requestMetadata,
+ data: {
+ emailType: recipientEmailType,
+ recipientEmail: recipient.email,
+ recipientName: recipient.name,
+ recipientRole: recipient.role,
+ recipientId: recipient.id,
+ isResending: true,
+ },
+ }),
+ });
+ },
+ { timeout: 30_000 },
+ );
}),
);
};
diff --git a/packages/lib/server-only/document/send-completed-email.ts b/packages/lib/server-only/document/send-completed-email.ts
index ad8207303..812e54ba3 100644
--- a/packages/lib/server-only/document/send-completed-email.ts
+++ b/packages/lib/server-only/document/send-completed-email.ts
@@ -49,44 +49,47 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
downloadLink: `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${token}/complete`,
});
- await prisma.$transaction(async (tx) => {
- await mailer.sendMail({
- to: {
- address: email,
- name,
- },
- from: {
- name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
- address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
- },
- subject: 'Signing Complete!',
- html: render(template),
- text: render(template, { plainText: true }),
- attachments: [
- {
- filename: document.title,
- content: Buffer.from(buffer),
+ await prisma.$transaction(
+ async (tx) => {
+ await mailer.sendMail({
+ to: {
+ address: email,
+ name,
},
- ],
- });
+ from: {
+ name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
+ address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
+ },
+ subject: 'Signing Complete!',
+ html: render(template),
+ text: render(template, { plainText: true }),
+ attachments: [
+ {
+ filename: document.title,
+ content: Buffer.from(buffer),
+ },
+ ],
+ });
- await tx.documentAuditLog.create({
- data: createDocumentAuditLogData({
- type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
- documentId: document.id,
- user: null,
- requestMetadata,
- data: {
- emailType: 'DOCUMENT_COMPLETED',
- recipientEmail: recipient.email,
- recipientName: recipient.name,
- recipientId: recipient.id,
- recipientRole: recipient.role,
- isResending: false,
- },
- }),
- });
- });
+ await tx.documentAuditLog.create({
+ data: createDocumentAuditLogData({
+ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
+ documentId: document.id,
+ user: null,
+ requestMetadata,
+ data: {
+ emailType: 'DOCUMENT_COMPLETED',
+ recipientEmail: recipient.email,
+ recipientName: recipient.name,
+ recipientId: recipient.id,
+ recipientRole: recipient.role,
+ isResending: false,
+ },
+ }),
+ });
+ },
+ { timeout: 30_000 },
+ );
}),
);
};
diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx
index be26ffcaf..4d85a8f32 100644
--- a/packages/lib/server-only/document/send-document.tsx
+++ b/packages/lib/server-only/document/send-document.tsx
@@ -108,49 +108,52 @@ export const sendDocument = async ({
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
- await prisma.$transaction(async (tx) => {
- await mailer.sendMail({
- to: {
- address: email,
- name,
- },
- from: {
- name: FROM_NAME,
- address: FROM_ADDRESS,
- },
- subject: customEmail?.subject
- ? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate)
- : `Please ${actionVerb.toLowerCase()} this document`,
- html: render(template),
- text: render(template, { plainText: true }),
- });
-
- await tx.recipient.update({
- where: {
- id: recipient.id,
- },
- data: {
- sendStatus: SendStatus.SENT,
- },
- });
-
- await tx.documentAuditLog.create({
- data: createDocumentAuditLogData({
- type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
- documentId: document.id,
- user,
- requestMetadata,
- data: {
- emailType: recipientEmailType,
- recipientEmail: recipient.email,
- recipientName: recipient.name,
- recipientRole: recipient.role,
- recipientId: recipient.id,
- isResending: false,
+ await prisma.$transaction(
+ async (tx) => {
+ await mailer.sendMail({
+ to: {
+ address: email,
+ name,
},
- }),
- });
- });
+ from: {
+ name: FROM_NAME,
+ address: FROM_ADDRESS,
+ },
+ subject: customEmail?.subject
+ ? renderCustomEmailTemplate(customEmail.subject, customEmailTemplate)
+ : `Please ${actionVerb.toLowerCase()} this document`,
+ html: render(template),
+ text: render(template, { plainText: true }),
+ });
+
+ await tx.recipient.update({
+ where: {
+ id: recipient.id,
+ },
+ data: {
+ sendStatus: SendStatus.SENT,
+ },
+ });
+
+ await tx.documentAuditLog.create({
+ data: createDocumentAuditLogData({
+ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
+ documentId: document.id,
+ user,
+ requestMetadata,
+ data: {
+ emailType: recipientEmailType,
+ recipientEmail: recipient.email,
+ recipientName: recipient.name,
+ recipientRole: recipient.role,
+ recipientId: recipient.id,
+ isResending: false,
+ },
+ }),
+ });
+ },
+ { timeout: 30_000 },
+ );
}),
);
diff --git a/packages/lib/server-only/document/update-title.ts b/packages/lib/server-only/document/update-title.ts
index 3e934e7be..f7f7a6b88 100644
--- a/packages/lib/server-only/document/update-title.ts
+++ b/packages/lib/server-only/document/update-title.ts
@@ -24,34 +24,38 @@ export const updateTitle = async ({
},
});
- return await prisma.$transaction(async (tx) => {
- const document = await tx.document.findFirstOrThrow({
- where: {
- id: documentId,
- OR: [
- {
- userId,
- },
- {
- team: {
- members: {
- some: {
- userId,
- },
+ const document = await prisma.document.findFirstOrThrow({
+ where: {
+ id: documentId,
+ OR: [
+ {
+ userId,
+ },
+ {
+ team: {
+ members: {
+ some: {
+ userId,
},
},
},
- ],
- },
- });
+ },
+ ],
+ },
+ });
- if (document.title === title) {
- return document;
- }
+ if (document.title === title) {
+ return document;
+ }
+ return await prisma.$transaction(async (tx) => {
+ // Instead of doing everything in a transaction we can use our knowledge
+ // of the current document title to ensure we aren't performing a conflicting
+ // update.
const updatedDocument = await tx.document.update({
where: {
id: documentId,
+ title: document.title,
},
data: {
title,
diff --git a/packages/lib/server-only/team/accept-team-invitation.ts b/packages/lib/server-only/team/accept-team-invitation.ts
index 4bfd31dfa..31fef5967 100644
--- a/packages/lib/server-only/team/accept-team-invitation.ts
+++ b/packages/lib/server-only/team/accept-team-invitation.ts
@@ -9,55 +9,58 @@ export type AcceptTeamInvitationOptions = {
};
export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitationOptions) => {
- await prisma.$transaction(async (tx) => {
- const user = await tx.user.findFirstOrThrow({
- where: {
- id: userId,
- },
- });
+ await prisma.$transaction(
+ async (tx) => {
+ const user = await tx.user.findFirstOrThrow({
+ where: {
+ id: userId,
+ },
+ });
- const teamMemberInvite = await tx.teamMemberInvite.findFirstOrThrow({
- where: {
- teamId,
- email: user.email,
- },
- include: {
- team: {
- include: {
- subscription: true,
+ const teamMemberInvite = await tx.teamMemberInvite.findFirstOrThrow({
+ where: {
+ teamId,
+ email: user.email,
+ },
+ include: {
+ team: {
+ include: {
+ subscription: true,
+ },
},
},
- },
- });
+ });
- const { team } = teamMemberInvite;
+ const { team } = teamMemberInvite;
- await tx.teamMember.create({
- data: {
- teamId: teamMemberInvite.teamId,
- userId: user.id,
- role: teamMemberInvite.role,
- },
- });
-
- await tx.teamMemberInvite.delete({
- where: {
- id: teamMemberInvite.id,
- },
- });
-
- if (IS_BILLING_ENABLED() && team.subscription) {
- const numberOfSeats = await tx.teamMember.count({
- where: {
+ await tx.teamMember.create({
+ data: {
teamId: teamMemberInvite.teamId,
+ userId: user.id,
+ role: teamMemberInvite.role,
},
});
- await updateSubscriptionItemQuantity({
- priceId: team.subscription.priceId,
- subscriptionId: team.subscription.planId,
- quantity: numberOfSeats,
+ await tx.teamMemberInvite.delete({
+ where: {
+ id: teamMemberInvite.id,
+ },
});
- }
- });
+
+ if (IS_BILLING_ENABLED() && team.subscription) {
+ const numberOfSeats = await tx.teamMember.count({
+ where: {
+ teamId: teamMemberInvite.teamId,
+ },
+ });
+
+ await updateSubscriptionItemQuantity({
+ priceId: team.subscription.priceId,
+ subscriptionId: team.subscription.planId,
+ quantity: numberOfSeats,
+ });
+ }
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/team/create-team-email-verification.ts b/packages/lib/server-only/team/create-team-email-verification.ts
index 28e1538d0..86cded7a9 100644
--- a/packages/lib/server-only/team/create-team-email-verification.ts
+++ b/packages/lib/server-only/team/create-team-email-verification.ts
@@ -28,56 +28,59 @@ export const createTeamEmailVerification = async ({
data,
}: CreateTeamEmailVerificationOptions) => {
try {
- await prisma.$transaction(async (tx) => {
- const team = await tx.team.findFirstOrThrow({
- where: {
- id: teamId,
- members: {
- some: {
- userId,
- role: {
- in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ await prisma.$transaction(
+ async (tx) => {
+ const team = await tx.team.findFirstOrThrow({
+ where: {
+ id: teamId,
+ members: {
+ some: {
+ userId,
+ role: {
+ in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ },
},
},
},
- },
- include: {
- teamEmail: true,
- emailVerification: true,
- },
- });
+ include: {
+ teamEmail: true,
+ emailVerification: true,
+ },
+ });
- if (team.teamEmail || team.emailVerification) {
- throw new AppError(
- AppErrorCode.INVALID_REQUEST,
- 'Team already has an email or existing email verification.',
- );
- }
+ if (team.teamEmail || team.emailVerification) {
+ throw new AppError(
+ AppErrorCode.INVALID_REQUEST,
+ 'Team already has an email or existing email verification.',
+ );
+ }
- const existingTeamEmail = await tx.teamEmail.findFirst({
- where: {
- email: data.email,
- },
- });
+ const existingTeamEmail = await tx.teamEmail.findFirst({
+ where: {
+ email: data.email,
+ },
+ });
- if (existingTeamEmail) {
- throw new AppError(AppErrorCode.ALREADY_EXISTS, 'Email already taken by another team.');
- }
+ if (existingTeamEmail) {
+ throw new AppError(AppErrorCode.ALREADY_EXISTS, 'Email already taken by another team.');
+ }
- const { token, expiresAt } = createTokenVerification({ hours: 1 });
+ const { token, expiresAt } = createTokenVerification({ hours: 1 });
- await tx.teamEmailVerification.create({
- data: {
- token,
- expiresAt,
- email: data.email,
- name: data.name,
- teamId,
- },
- });
+ await tx.teamEmailVerification.create({
+ data: {
+ token,
+ expiresAt,
+ email: data.email,
+ name: data.name,
+ teamId,
+ },
+ });
- await sendTeamEmailVerificationEmail(data.email, token, team.name, team.url);
- });
+ await sendTeamEmailVerificationEmail(data.email, token, team.name, team.url);
+ },
+ { timeout: 30_000 },
+ );
} catch (err) {
console.error(err);
diff --git a/packages/lib/server-only/team/delete-team-members.ts b/packages/lib/server-only/team/delete-team-members.ts
index 162d7de53..14b75a473 100644
--- a/packages/lib/server-only/team/delete-team-members.ts
+++ b/packages/lib/server-only/team/delete-team-members.ts
@@ -27,76 +27,81 @@ export const deleteTeamMembers = async ({
teamId,
teamMemberIds,
}: DeleteTeamMembersOptions) => {
- await prisma.$transaction(async (tx) => {
- // Find the team and validate that the user is allowed to remove members.
- const team = await tx.team.findFirstOrThrow({
- where: {
- id: teamId,
- members: {
- some: {
- userId,
- role: {
- in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ await prisma.$transaction(
+ async (tx) => {
+ // Find the team and validate that the user is allowed to remove members.
+ const team = await tx.team.findFirstOrThrow({
+ where: {
+ id: teamId,
+ members: {
+ some: {
+ userId,
+ role: {
+ in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ },
},
},
},
- },
- include: {
- members: {
- select: {
- id: true,
- userId: true,
- role: true,
+ include: {
+ members: {
+ select: {
+ id: true,
+ userId: true,
+ role: true,
+ },
+ },
+ subscription: true,
+ },
+ });
+
+ const currentTeamMember = team.members.find((member) => member.userId === userId);
+ const teamMembersToRemove = team.members.filter((member) =>
+ teamMemberIds.includes(member.id),
+ );
+
+ if (!currentTeamMember) {
+ throw new AppError(AppErrorCode.NOT_FOUND, 'Team member record does not exist');
+ }
+
+ if (teamMembersToRemove.find((member) => member.userId === team.ownerUserId)) {
+ throw new AppError(AppErrorCode.UNAUTHORIZED, 'Cannot remove the team owner');
+ }
+
+ const isMemberToRemoveHigherRole = teamMembersToRemove.some(
+ (member) => !isTeamRoleWithinUserHierarchy(currentTeamMember.role, member.role),
+ );
+
+ if (isMemberToRemoveHigherRole) {
+ throw new AppError(AppErrorCode.UNAUTHORIZED, 'Cannot remove a member with a higher role');
+ }
+
+ // Remove the team members.
+ await tx.teamMember.deleteMany({
+ where: {
+ id: {
+ in: teamMemberIds,
+ },
+ teamId,
+ userId: {
+ not: team.ownerUserId,
},
},
- subscription: true,
- },
- });
-
- const currentTeamMember = team.members.find((member) => member.userId === userId);
- const teamMembersToRemove = team.members.filter((member) => teamMemberIds.includes(member.id));
-
- if (!currentTeamMember) {
- throw new AppError(AppErrorCode.NOT_FOUND, 'Team member record does not exist');
- }
-
- if (teamMembersToRemove.find((member) => member.userId === team.ownerUserId)) {
- throw new AppError(AppErrorCode.UNAUTHORIZED, 'Cannot remove the team owner');
- }
-
- const isMemberToRemoveHigherRole = teamMembersToRemove.some(
- (member) => !isTeamRoleWithinUserHierarchy(currentTeamMember.role, member.role),
- );
-
- if (isMemberToRemoveHigherRole) {
- throw new AppError(AppErrorCode.UNAUTHORIZED, 'Cannot remove a member with a higher role');
- }
-
- // Remove the team members.
- await tx.teamMember.deleteMany({
- where: {
- id: {
- in: teamMemberIds,
- },
- teamId,
- userId: {
- not: team.ownerUserId,
- },
- },
- });
-
- if (IS_BILLING_ENABLED() && team.subscription) {
- const numberOfSeats = await tx.teamMember.count({
- where: {
- teamId,
- },
});
- await updateSubscriptionItemQuantity({
- priceId: team.subscription.priceId,
- subscriptionId: team.subscription.planId,
- quantity: numberOfSeats,
- });
- }
- });
+ if (IS_BILLING_ENABLED() && team.subscription) {
+ const numberOfSeats = await tx.teamMember.count({
+ where: {
+ teamId,
+ },
+ });
+
+ await updateSubscriptionItemQuantity({
+ priceId: team.subscription.priceId,
+ subscriptionId: team.subscription.planId,
+ quantity: numberOfSeats,
+ });
+ }
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/team/delete-team.ts b/packages/lib/server-only/team/delete-team.ts
index dffc044d8..667d2f448 100644
--- a/packages/lib/server-only/team/delete-team.ts
+++ b/packages/lib/server-only/team/delete-team.ts
@@ -9,34 +9,37 @@ export type DeleteTeamOptions = {
};
export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
- await prisma.$transaction(async (tx) => {
- const team = await tx.team.findFirstOrThrow({
- where: {
- id: teamId,
- ownerUserId: userId,
- },
- include: {
- subscription: true,
- },
- });
+ await prisma.$transaction(
+ async (tx) => {
+ const team = await tx.team.findFirstOrThrow({
+ where: {
+ id: teamId,
+ ownerUserId: userId,
+ },
+ include: {
+ subscription: true,
+ },
+ });
- if (team.subscription) {
- await stripe.subscriptions
- .cancel(team.subscription.planId, {
- prorate: false,
- invoice_now: true,
- })
- .catch((err) => {
- console.error(err);
- throw AppError.parseError(err);
- });
- }
+ if (team.subscription) {
+ await stripe.subscriptions
+ .cancel(team.subscription.planId, {
+ prorate: false,
+ invoice_now: true,
+ })
+ .catch((err) => {
+ console.error(err);
+ throw AppError.parseError(err);
+ });
+ }
- await tx.team.delete({
- where: {
- id: teamId,
- ownerUserId: userId,
- },
- });
- });
+ await tx.team.delete({
+ where: {
+ id: teamId,
+ ownerUserId: userId,
+ },
+ });
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/team/leave-team.ts b/packages/lib/server-only/team/leave-team.ts
index 509c38950..410d0707c 100644
--- a/packages/lib/server-only/team/leave-team.ts
+++ b/packages/lib/server-only/team/leave-team.ts
@@ -15,45 +15,48 @@ export type LeaveTeamOptions = {
};
export const leaveTeam = async ({ userId, teamId }: LeaveTeamOptions) => {
- await prisma.$transaction(async (tx) => {
- const team = await tx.team.findFirstOrThrow({
- where: {
- id: teamId,
- ownerUserId: {
- not: userId,
- },
- },
- include: {
- subscription: true,
- },
- });
-
- await tx.teamMember.delete({
- where: {
- userId_teamId: {
- userId,
- teamId,
- },
- team: {
+ await prisma.$transaction(
+ async (tx) => {
+ const team = await tx.team.findFirstOrThrow({
+ where: {
+ id: teamId,
ownerUserId: {
not: userId,
},
},
- },
- });
-
- if (IS_BILLING_ENABLED() && team.subscription) {
- const numberOfSeats = await tx.teamMember.count({
- where: {
- teamId,
+ include: {
+ subscription: true,
},
});
- await updateSubscriptionItemQuantity({
- priceId: team.subscription.priceId,
- subscriptionId: team.subscription.planId,
- quantity: numberOfSeats,
+ await tx.teamMember.delete({
+ where: {
+ userId_teamId: {
+ userId,
+ teamId,
+ },
+ team: {
+ ownerUserId: {
+ not: userId,
+ },
+ },
+ },
});
- }
- });
+
+ if (IS_BILLING_ENABLED() && team.subscription) {
+ const numberOfSeats = await tx.teamMember.count({
+ where: {
+ teamId,
+ },
+ });
+
+ await updateSubscriptionItemQuantity({
+ priceId: team.subscription.priceId,
+ subscriptionId: team.subscription.planId,
+ quantity: numberOfSeats,
+ });
+ }
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/team/request-team-ownership-transfer.ts b/packages/lib/server-only/team/request-team-ownership-transfer.ts
index 7da976ee1..92fd5b61e 100644
--- a/packages/lib/server-only/team/request-team-ownership-transfer.ts
+++ b/packages/lib/server-only/team/request-team-ownership-transfer.ts
@@ -44,63 +44,66 @@ export const requestTeamOwnershipTransfer = async ({
// Todo: Clear payment methods disabled for now.
const clearPaymentMethods = false;
- await prisma.$transaction(async (tx) => {
- const team = await tx.team.findFirstOrThrow({
- where: {
- id: teamId,
- ownerUserId: userId,
- members: {
- some: {
- userId: newOwnerUserId,
+ await prisma.$transaction(
+ async (tx) => {
+ const team = await tx.team.findFirstOrThrow({
+ where: {
+ id: teamId,
+ ownerUserId: userId,
+ members: {
+ some: {
+ userId: newOwnerUserId,
+ },
},
},
- },
- });
+ });
- const newOwnerUser = await tx.user.findFirstOrThrow({
- where: {
- id: newOwnerUserId,
- },
- });
+ const newOwnerUser = await tx.user.findFirstOrThrow({
+ where: {
+ id: newOwnerUserId,
+ },
+ });
- const { token, expiresAt } = createTokenVerification({ minute: 10 });
+ const { token, expiresAt } = createTokenVerification({ minute: 10 });
- const teamVerificationPayload = {
- teamId,
- token,
- expiresAt,
- userId: newOwnerUserId,
- name: newOwnerUser.name ?? '',
- email: newOwnerUser.email,
- clearPaymentMethods,
- };
-
- await tx.teamTransferVerification.upsert({
- where: {
+ const teamVerificationPayload = {
teamId,
- },
- create: teamVerificationPayload,
- update: teamVerificationPayload,
- });
+ token,
+ expiresAt,
+ userId: newOwnerUserId,
+ name: newOwnerUser.name ?? '',
+ email: newOwnerUser.email,
+ clearPaymentMethods,
+ };
- const template = createElement(TeamTransferRequestTemplate, {
- assetBaseUrl: WEBAPP_BASE_URL,
- baseUrl: WEBAPP_BASE_URL,
- senderName: userName,
- teamName: team.name,
- teamUrl: team.url,
- token,
- });
+ await tx.teamTransferVerification.upsert({
+ where: {
+ teamId,
+ },
+ create: teamVerificationPayload,
+ update: teamVerificationPayload,
+ });
- await mailer.sendMail({
- to: newOwnerUser.email,
- from: {
- name: FROM_NAME,
- address: FROM_ADDRESS,
- },
- subject: `You have been requested to take ownership of team ${team.name} on Documenso`,
- html: render(template),
- text: render(template, { plainText: true }),
- });
- });
+ const template = createElement(TeamTransferRequestTemplate, {
+ assetBaseUrl: WEBAPP_BASE_URL,
+ baseUrl: WEBAPP_BASE_URL,
+ senderName: userName,
+ teamName: team.name,
+ teamUrl: team.url,
+ token,
+ });
+
+ await mailer.sendMail({
+ to: newOwnerUser.email,
+ from: {
+ name: FROM_NAME,
+ address: FROM_ADDRESS,
+ },
+ subject: `You have been requested to take ownership of team ${team.name} on Documenso`,
+ html: render(template),
+ text: render(template, { plainText: true }),
+ });
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/team/resend-team-email-verification.ts b/packages/lib/server-only/team/resend-team-email-verification.ts
index 55afe61ce..b1d6ffe99 100644
--- a/packages/lib/server-only/team/resend-team-email-verification.ts
+++ b/packages/lib/server-only/team/resend-team-email-verification.ts
@@ -17,49 +17,52 @@ export const resendTeamEmailVerification = async ({
userId,
teamId,
}: ResendTeamMemberInvitationOptions) => {
- await prisma.$transaction(async (tx) => {
- const team = await tx.team.findUniqueOrThrow({
- where: {
- id: teamId,
- members: {
- some: {
- userId,
- role: {
- in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ await prisma.$transaction(
+ async (tx) => {
+ const team = await tx.team.findUniqueOrThrow({
+ where: {
+ id: teamId,
+ members: {
+ some: {
+ userId,
+ role: {
+ in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ },
},
},
},
- },
- include: {
- emailVerification: true,
- },
- });
+ include: {
+ emailVerification: true,
+ },
+ });
- if (!team) {
- throw new AppError('TeamNotFound', 'User is not a member of the team.');
- }
+ if (!team) {
+ throw new AppError('TeamNotFound', 'User is not a member of the team.');
+ }
- const { emailVerification } = team;
+ const { emailVerification } = team;
- if (!emailVerification) {
- throw new AppError(
- 'VerificationNotFound',
- 'No team email verification exists for this team.',
- );
- }
+ if (!emailVerification) {
+ throw new AppError(
+ 'VerificationNotFound',
+ 'No team email verification exists for this team.',
+ );
+ }
- const { token, expiresAt } = createTokenVerification({ hours: 1 });
+ const { token, expiresAt } = createTokenVerification({ hours: 1 });
- await tx.teamEmailVerification.update({
- where: {
- teamId,
- },
- data: {
- token,
- expiresAt,
- },
- });
+ await tx.teamEmailVerification.update({
+ where: {
+ teamId,
+ },
+ data: {
+ token,
+ expiresAt,
+ },
+ });
- await sendTeamEmailVerificationEmail(emailVerification.email, token, team.name, team.url);
- });
+ await sendTeamEmailVerificationEmail(emailVerification.email, token, team.name, team.url);
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/team/resend-team-member-invitation.ts b/packages/lib/server-only/team/resend-team-member-invitation.ts
index fb860ccc0..897154a2a 100644
--- a/packages/lib/server-only/team/resend-team-member-invitation.ts
+++ b/packages/lib/server-only/team/resend-team-member-invitation.ts
@@ -35,42 +35,45 @@ export const resendTeamMemberInvitation = async ({
teamId,
invitationId,
}: ResendTeamMemberInvitationOptions) => {
- await prisma.$transaction(async (tx) => {
- const team = await tx.team.findUniqueOrThrow({
- where: {
- id: teamId,
- members: {
- some: {
- userId,
- role: {
- in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ await prisma.$transaction(
+ async (tx) => {
+ const team = await tx.team.findUniqueOrThrow({
+ where: {
+ id: teamId,
+ members: {
+ some: {
+ userId,
+ role: {
+ in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
+ },
},
},
},
- },
- });
+ });
- if (!team) {
- throw new AppError('TeamNotFound', 'User is not a valid member of the team.');
- }
+ if (!team) {
+ throw new AppError('TeamNotFound', 'User is not a valid member of the team.');
+ }
- const teamMemberInvite = await tx.teamMemberInvite.findUniqueOrThrow({
- where: {
- id: invitationId,
- teamId,
- },
- });
+ const teamMemberInvite = await tx.teamMemberInvite.findUniqueOrThrow({
+ where: {
+ id: invitationId,
+ teamId,
+ },
+ });
- if (!teamMemberInvite) {
- throw new AppError('InviteNotFound', 'No invite exists for this user.');
- }
+ if (!teamMemberInvite) {
+ throw new AppError('InviteNotFound', 'No invite exists for this user.');
+ }
- await sendTeamMemberInviteEmail({
- email: teamMemberInvite.email,
- token: teamMemberInvite.token,
- teamName: team.name,
- teamUrl: team.url,
- senderName: userName,
- });
- });
+ await sendTeamMemberInviteEmail({
+ email: teamMemberInvite.email,
+ token: teamMemberInvite.token,
+ teamName: team.name,
+ teamUrl: team.url,
+ senderName: userName,
+ });
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/team/transfer-team-ownership.ts b/packages/lib/server-only/team/transfer-team-ownership.ts
index 7b886d323..b87c04b91 100644
--- a/packages/lib/server-only/team/transfer-team-ownership.ts
+++ b/packages/lib/server-only/team/transfer-team-ownership.ts
@@ -11,78 +11,81 @@ export type TransferTeamOwnershipOptions = {
};
export const transferTeamOwnership = async ({ token }: TransferTeamOwnershipOptions) => {
- await prisma.$transaction(async (tx) => {
- const teamTransferVerification = await tx.teamTransferVerification.findFirstOrThrow({
- where: {
- token,
- },
- include: {
- team: {
- include: {
- subscription: true,
+ await prisma.$transaction(
+ async (tx) => {
+ const teamTransferVerification = await tx.teamTransferVerification.findFirstOrThrow({
+ where: {
+ token,
+ },
+ include: {
+ team: {
+ include: {
+ subscription: true,
+ },
},
},
- },
- });
-
- const { team, userId: newOwnerUserId } = teamTransferVerification;
-
- await tx.teamTransferVerification.delete({
- where: {
- teamId: team.id,
- },
- });
-
- const newOwnerUser = await tx.user.findFirstOrThrow({
- where: {
- id: newOwnerUserId,
- teamMembers: {
- some: {
- teamId: team.id,
- },
- },
- },
- include: {
- Subscription: true,
- },
- });
-
- let teamSubscription: Stripe.Subscription | null = null;
-
- if (IS_BILLING_ENABLED()) {
- teamSubscription = await transferTeamSubscription({
- user: newOwnerUser,
- team,
- clearPaymentMethods: teamTransferVerification.clearPaymentMethods,
});
- }
- if (teamSubscription) {
- await tx.subscription.upsert(
- mapStripeSubscriptionToPrismaUpsertAction(teamSubscription, undefined, team.id),
- );
- }
+ const { team, userId: newOwnerUserId } = teamTransferVerification;
- await tx.team.update({
- where: {
- id: team.id,
- },
- data: {
- ownerUserId: newOwnerUserId,
- members: {
- update: {
- where: {
- userId_teamId: {
- teamId: team.id,
- userId: newOwnerUserId,
+ await tx.teamTransferVerification.delete({
+ where: {
+ teamId: team.id,
+ },
+ });
+
+ const newOwnerUser = await tx.user.findFirstOrThrow({
+ where: {
+ id: newOwnerUserId,
+ teamMembers: {
+ some: {
+ teamId: team.id,
+ },
+ },
+ },
+ include: {
+ Subscription: true,
+ },
+ });
+
+ let teamSubscription: Stripe.Subscription | null = null;
+
+ if (IS_BILLING_ENABLED()) {
+ teamSubscription = await transferTeamSubscription({
+ user: newOwnerUser,
+ team,
+ clearPaymentMethods: teamTransferVerification.clearPaymentMethods,
+ });
+ }
+
+ if (teamSubscription) {
+ await tx.subscription.upsert(
+ mapStripeSubscriptionToPrismaUpsertAction(teamSubscription, undefined, team.id),
+ );
+ }
+
+ await tx.team.update({
+ where: {
+ id: team.id,
+ },
+ data: {
+ ownerUserId: newOwnerUserId,
+ members: {
+ update: {
+ where: {
+ userId_teamId: {
+ teamId: team.id,
+ userId: newOwnerUserId,
+ },
+ },
+ data: {
+ role: TeamMemberRole.ADMIN,
},
},
- data: {
- role: TeamMemberRole.ADMIN,
- },
},
},
- },
- });
- });
+ });
+ },
+ { timeout: 30_000 },
+ );
};
diff --git a/packages/lib/server-only/user/create-user.ts b/packages/lib/server-only/user/create-user.ts
index 9a40696db..1852dc12e 100644
--- a/packages/lib/server-only/user/create-user.ts
+++ b/packages/lib/server-only/user/create-user.ts
@@ -53,47 +53,50 @@ export const createUser = async ({ name, email, password, signature }: CreateUse
await Promise.allSettled(
acceptedTeamInvites.map(async (invite) =>
prisma
- .$transaction(async (tx) => {
- await tx.teamMember.create({
- data: {
- teamId: invite.teamId,
- userId: user.id,
- role: invite.role,
- },
- });
-
- await tx.teamMemberInvite.delete({
- where: {
- id: invite.id,
- },
- });
-
- if (!IS_BILLING_ENABLED()) {
- return;
- }
-
- const team = await tx.team.findFirstOrThrow({
- where: {
- id: invite.teamId,
- },
- include: {
- members: {
- select: {
- id: true,
- },
+ .$transaction(
+ async (tx) => {
+ await tx.teamMember.create({
+ data: {
+ teamId: invite.teamId,
+ userId: user.id,
+ role: invite.role,
},
- subscription: true,
- },
- });
-
- if (team.subscription) {
- await updateSubscriptionItemQuantity({
- priceId: team.subscription.priceId,
- subscriptionId: team.subscription.planId,
- quantity: team.members.length,
});
- }
- })
+
+ await tx.teamMemberInvite.delete({
+ where: {
+ id: invite.id,
+ },
+ });
+
+ if (!IS_BILLING_ENABLED()) {
+ return;
+ }
+
+ const team = await tx.team.findFirstOrThrow({
+ where: {
+ id: invite.teamId,
+ },
+ include: {
+ members: {
+ select: {
+ id: true,
+ },
+ },
+ subscription: true,
+ },
+ });
+
+ if (team.subscription) {
+ await updateSubscriptionItemQuantity({
+ priceId: team.subscription.priceId,
+ subscriptionId: team.subscription.planId,
+ quantity: team.members.length,
+ });
+ }
+ },
+ { timeout: 30_000 },
+ )
.catch(async () => {
await prisma.teamMemberInvite.update({
where: {