mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 17:04:12 +10:00
Merge branch 'main' into fix/cc-recipient-order-last
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { ORGANISATION_USER_ACCOUNT_TYPE } from '../../constants/organisations';
|
||||
import { jobs } from '../../jobs/client';
|
||||
import { orphanEnvelopes } from '../envelope/orphan-envelopes';
|
||||
|
||||
export type DeleteOrganisationOptions = {
|
||||
organisation: {
|
||||
id: string;
|
||||
teams: { id: number }[];
|
||||
subscription: { planId: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Fully tears down an organisation:
|
||||
*
|
||||
* 1. Orphans every team's envelopes (so foreign key constraints don't block the delete).
|
||||
* 2. Removes the organisation's account rows and the organisation itself in a transaction.
|
||||
* 3. Schedules the Stripe subscription to be cancelled at the end of the billing period
|
||||
* (when one exists). The job runs asynchronously so a Stripe outage doesn't block the
|
||||
* delete, and is retried by the job runner if Stripe is temporarily unavailable.
|
||||
*
|
||||
* Authorization must be handled by the caller. This is the shared implementation used by
|
||||
* the organisation delete route, the admin delete-organisation job, and account deletion.
|
||||
*/
|
||||
export const deleteOrganisation = async ({ organisation }: DeleteOrganisationOptions) => {
|
||||
// Orphan all envelopes to get rid of foreign key constraints.
|
||||
await Promise.all(organisation.teams.map(async (team) => orphanEnvelopes({ teamId: team.id })));
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.account.deleteMany({
|
||||
where: {
|
||||
type: ORGANISATION_USER_ACCOUNT_TYPE,
|
||||
provider: organisation.id,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.organisation.delete({
|
||||
where: {
|
||||
id: organisation.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (organisation.subscription) {
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.cancel-organisation-subscription',
|
||||
payload: {
|
||||
stripeSubscriptionId: organisation.subscription.planId,
|
||||
organisationId: organisation.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { orphanEnvelopes } from '../envelope/orphan-envelopes';
|
||||
import { deleteOrganisation } from '../organisation/delete-organisation';
|
||||
|
||||
export type DeleteUserOptions = {
|
||||
id: number;
|
||||
@@ -20,6 +20,11 @@ export const deleteUser = async ({ id }: DeleteUserOptions) => {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
subscription: {
|
||||
select: {
|
||||
planId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
organisationMember: {
|
||||
@@ -44,9 +49,6 @@ export const deleteUser = async ({ id }: DeleteUserOptions) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Get team IDs from organisations the user owns.
|
||||
const ownedTeamIds = user.ownedOrganisations.flatMap((org) => org.teams.map((team) => team.id));
|
||||
|
||||
// Get team IDs from organisations the user is a member of (but not owner).
|
||||
const memberTeams = user.organisationMember
|
||||
.filter((member) => member.organisation.ownerUserId !== user.id)
|
||||
@@ -57,8 +59,13 @@ export const deleteUser = async ({ id }: DeleteUserOptions) => {
|
||||
})),
|
||||
);
|
||||
|
||||
// For teams where user is the org owner - orphan their envelopes.
|
||||
await Promise.all(ownedTeamIds.map(async (teamId) => orphanEnvelopes({ teamId })));
|
||||
// For organisations the user owns - fully tear them down (orphan envelopes,
|
||||
// delete the organisation, and cancel any Stripe subscription). Without this
|
||||
// the organisations would only cascade away when the user row is deleted,
|
||||
// leaving their subscriptions billing and account rows behind.
|
||||
for (const organisation of user.ownedOrganisations) {
|
||||
await deleteOrganisation({ organisation });
|
||||
}
|
||||
|
||||
// For teams where user is a member (not owner) - transfer envelopes to team owner.
|
||||
await Promise.all(
|
||||
|
||||
Reference in New Issue
Block a user