mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 00:43:40 +10:00
feat: add admin org deletion (#2795)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZDeleteOrganisationRequestSchema, ZDeleteOrganisationResponseSchema } from './delete-organisation.types';
|
||||
|
||||
export const deleteOrganisationRoute = adminProcedure
|
||||
.input(ZDeleteOrganisationRequestSchema)
|
||||
.output(ZDeleteOrganisationResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { organisationId, organisationName, sendEmailToOwner } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
sendEmailToOwner,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Organisation not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (organisation.name !== organisationName) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Organisation name does not match',
|
||||
});
|
||||
}
|
||||
|
||||
// The deletion itself is offloaded to a background job because orphaning
|
||||
// potentially-large numbers of envelopes can take a while.
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.admin-delete-organisation',
|
||||
payload: {
|
||||
organisationId: organisation.id,
|
||||
sendEmailToOwner,
|
||||
requestedByUserId: user.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteOrganisationRequestSchema = z.object({
|
||||
organisationId: z.string().min(1),
|
||||
/**
|
||||
* The organisation name as typed by the admin in the confirmation dialog.
|
||||
* Must exactly match the persisted organisation's name for the deletion
|
||||
* to proceed.
|
||||
*/
|
||||
organisationName: z.string().min(1),
|
||||
/**
|
||||
* Whether to email the organisation owner notifying them of the deletion.
|
||||
*/
|
||||
sendEmailToOwner: z.boolean(),
|
||||
});
|
||||
|
||||
export const ZDeleteOrganisationResponseSchema = z.void();
|
||||
|
||||
export type TDeleteOrganisationRequest = z.infer<typeof ZDeleteOrganisationRequestSchema>;
|
||||
export type TDeleteOrganisationResponse = z.infer<typeof ZDeleteOrganisationResponseSchema>;
|
||||
@@ -3,6 +3,7 @@ import { createAdminOrganisationRoute } from './create-admin-organisation';
|
||||
import { createStripeCustomerRoute } from './create-stripe-customer';
|
||||
import { createSubscriptionClaimRoute } from './create-subscription-claim';
|
||||
import { deleteDocumentRoute } from './delete-document';
|
||||
import { deleteOrganisationRoute } from './delete-organisation';
|
||||
import { deleteAdminOrganisationMemberRoute } from './delete-organisation-member';
|
||||
import { deleteSubscriptionClaimRoute } from './delete-subscription-claim';
|
||||
import { deleteAdminTeamMemberRoute } from './delete-team-member';
|
||||
@@ -41,6 +42,7 @@ export const adminRouter = router({
|
||||
get: getAdminOrganisationRoute,
|
||||
create: createAdminOrganisationRoute,
|
||||
update: updateAdminOrganisationRoute,
|
||||
delete: deleteOrganisationRoute,
|
||||
swapSubscription: swapOrganisationSubscriptionRoute,
|
||||
},
|
||||
organisationMember: {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ORGANISATION_USER_ACCOUNT_TYPE,
|
||||
} from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { orphanEnvelopes } from '@documenso/lib/server-only/envelope/orphan-envelopes';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
@@ -42,6 +43,11 @@ export const deleteOrganisationRoute = authenticatedProcedure
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
subscription: {
|
||||
select: {
|
||||
planId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -68,4 +74,18 @@ export const deleteOrganisationRoute = authenticatedProcedure
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// If the organisation has a Stripe subscription, schedule it to be
|
||||
// cancelled at the end of the current billing period. The job runs
|
||||
// asynchronously so a Stripe outage doesn't block deletion, and is
|
||||
// retried by the job runner if Stripe is temporarily unavailable.
|
||||
if (organisation.subscription) {
|
||||
await jobs.triggerJob({
|
||||
name: 'internal.cancel-organisation-subscription',
|
||||
payload: {
|
||||
stripeSubscriptionId: organisation.subscription.planId,
|
||||
organisationId: organisation.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user