mirror of
https://github.com/documenso/documenso.git
synced 2026-08-23 14:52:23 +10:00
fix: handle completion when already signed (#3159)
Previously attempting to complete a document which is already completed you'd get a generic error toast. Now when completing a document that you have already completed you are redirected to the completed page. Handles cases where two mutations managed to fire racing eachother.
This commit is contained in:
@@ -80,22 +80,29 @@ export const completeDocumentWithToken = async ({
|
||||
|
||||
const legacyDocumentId = mapSecondaryIdToDocumentId(envelope.secondaryId);
|
||||
|
||||
if (envelope.status !== DocumentStatus.PENDING) {
|
||||
throw new Error(`Document ${envelope.id} must be pending`);
|
||||
}
|
||||
|
||||
if (envelope.recipients.length === 0) {
|
||||
throw new Error(`Document ${envelope.id} has no recipient with token ${token}`);
|
||||
}
|
||||
|
||||
const [recipient] = envelope.recipients;
|
||||
|
||||
assertRecipientNotExpired(recipient);
|
||||
|
||||
// A retried or duplicate completion request for an already signed
|
||||
// recipient throws a code the router resolves idempotently. This must be
|
||||
// checked before the envelope status guard since the envelope may have
|
||||
// been completed and sealed by the recipient's original request.
|
||||
if (recipient.signingStatus === SigningStatus.SIGNED) {
|
||||
throw new Error(`Recipient ${recipient.id} has already signed`);
|
||||
throw new AppError(AppErrorCode.RECIPIENT_ALREADY_SIGNED, {
|
||||
message: `Recipient ${recipient.id} has already signed`,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (envelope.status !== DocumentStatus.PENDING) {
|
||||
throw new Error(`Document ${envelope.id} must be pending`);
|
||||
}
|
||||
|
||||
assertRecipientNotExpired(recipient);
|
||||
|
||||
if (recipient.signingStatus === SigningStatus.REJECTED) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Recipient has already rejected the document',
|
||||
@@ -276,9 +283,15 @@ export const completeDocumentWithToken = async ({
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.recipient.update({
|
||||
// Conditional update so two concurrent completion requests can't both
|
||||
// proceed: only the request that transitions the recipient to SIGNED
|
||||
// continues, the loser sees a count of 0 and aborts.
|
||||
const { count: updatedRecipientCount } = await tx.recipient.updateMany({
|
||||
where: {
|
||||
id: recipient.id,
|
||||
signingStatus: {
|
||||
not: SigningStatus.SIGNED,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
@@ -288,6 +301,16 @@ export const completeDocumentWithToken = async ({
|
||||
},
|
||||
});
|
||||
|
||||
// A concurrent request completed the recipient between our initial read
|
||||
// and this transaction. Abort so the winning request handles all side
|
||||
// effects, the router resolves this code idempotently.
|
||||
if (updatedRecipientCount === 0) {
|
||||
throw new AppError(AppErrorCode.RECIPIENT_ALREADY_SIGNED, {
|
||||
message: `Recipient ${recipient.id} has already signed`,
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (recipientEmail !== recipient.email || recipientName !== recipient.name) {
|
||||
await tx.documentAuditLog.create({
|
||||
data: createDocumentAuditLogData({
|
||||
|
||||
Reference in New Issue
Block a user