fix: make invite and confirmations long lived (#1309)
Previously we would delete all invites and confirmation tokens upon completing the action that they represent. This change instead adds a flag on each token indicating whether it has been completed so we can action a completed token differently in the UI to reduce confusion for users. This had been brought up a number of times where confirmation emails, team member invites and other items may have been actioned and forgotten about causing an error toast/page upon subsequent revisit.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { updateSubscriptionItemQuantity } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberInviteStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { jobs } from '../../jobs/client';
|
||||
|
||||
@@ -22,6 +23,9 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
where: {
|
||||
teamId,
|
||||
email: user.email,
|
||||
status: {
|
||||
not: TeamMemberInviteStatus.DECLINED,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
@@ -37,6 +41,10 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
},
|
||||
});
|
||||
|
||||
if (teamMemberInvite.status === TeamMemberInviteStatus.ACCEPTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { team } = teamMemberInvite;
|
||||
|
||||
const teamMember = await tx.teamMember.create({
|
||||
@@ -47,10 +55,13 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
},
|
||||
});
|
||||
|
||||
await tx.teamMemberInvite.delete({
|
||||
await tx.teamMemberInvite.update({
|
||||
where: {
|
||||
id: teamMemberInvite.id,
|
||||
},
|
||||
data: {
|
||||
status: TeamMemberInviteStatus.ACCEPTED,
|
||||
},
|
||||
});
|
||||
|
||||
if (IS_BILLING_ENABLED() && team.subscription) {
|
||||
|
||||
@@ -28,11 +28,24 @@ export const transferTeamOwnership = async ({ token }: TransferTeamOwnershipOpti
|
||||
|
||||
const { team, userId: newOwnerUserId } = teamTransferVerification;
|
||||
|
||||
await tx.teamTransferVerification.delete({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
});
|
||||
await Promise.all([
|
||||
tx.teamTransferVerification.updateMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
}),
|
||||
tx.teamTransferVerification.deleteMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
expiresAt: {
|
||||
lt: new Date(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const newOwnerUser = await tx.user.findFirstOrThrow({
|
||||
where: {
|
||||
|
||||
@@ -4,6 +4,13 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { jobsClient } from '../../jobs/client';
|
||||
|
||||
export const EMAIL_VERIFICATION_STATE = {
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
VERIFIED: 'VERIFIED',
|
||||
EXPIRED: 'EXPIRED',
|
||||
ALREADY_VERIFIED: 'ALREADY_VERIFIED',
|
||||
} as const;
|
||||
|
||||
export type VerifyEmailProps = {
|
||||
token: string;
|
||||
};
|
||||
@@ -19,7 +26,7 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
});
|
||||
|
||||
if (!verificationToken) {
|
||||
return null;
|
||||
return EMAIL_VERIFICATION_STATE.NOT_FOUND;
|
||||
}
|
||||
|
||||
// check if the token is valid or expired
|
||||
@@ -48,10 +55,14 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
});
|
||||
}
|
||||
|
||||
return valid;
|
||||
return EMAIL_VERIFICATION_STATE.EXPIRED;
|
||||
}
|
||||
|
||||
const [updatedUser, deletedToken] = await prisma.$transaction([
|
||||
if (verificationToken.completed) {
|
||||
return EMAIL_VERIFICATION_STATE.ALREADY_VERIFIED;
|
||||
}
|
||||
|
||||
const [updatedUser] = await prisma.$transaction([
|
||||
prisma.user.update({
|
||||
where: {
|
||||
id: verificationToken.userId,
|
||||
@@ -60,16 +71,28 @@ export const verifyEmail = async ({ token }: VerifyEmailProps) => {
|
||||
emailVerified: new Date(),
|
||||
},
|
||||
}),
|
||||
prisma.verificationToken.updateMany({
|
||||
where: {
|
||||
userId: verificationToken.userId,
|
||||
},
|
||||
data: {
|
||||
completed: true,
|
||||
},
|
||||
}),
|
||||
// Tidy up old expired tokens
|
||||
prisma.verificationToken.deleteMany({
|
||||
where: {
|
||||
userId: verificationToken.userId,
|
||||
expires: {
|
||||
lt: new Date(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!updatedUser || !deletedToken) {
|
||||
if (!updatedUser) {
|
||||
throw new Error('Something went wrong while verifying your email. Please try again.');
|
||||
}
|
||||
|
||||
return !!updatedUser && !!deletedToken;
|
||||
return EMAIL_VERIFICATION_STATE.VERIFIED;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user