mirror of
https://github.com/documenso/documenso.git
synced 2025-11-21 04:01:45 +10:00
feat: billing
This commit is contained in:
@ -1,54 +0,0 @@
|
||||
import { getCheckoutSession } from '@documenso/ee/server-only/stripe/get-checkout-session';
|
||||
import { getTeamPrices } from '@documenso/ee/server-only/stripe/get-team-prices';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type CreateTeamPendingCheckoutSession = {
|
||||
userId: number;
|
||||
pendingTeamId: number;
|
||||
interval: 'monthly' | 'yearly';
|
||||
};
|
||||
|
||||
export const createTeamPendingCheckoutSession = async ({
|
||||
userId,
|
||||
pendingTeamId,
|
||||
interval,
|
||||
}: CreateTeamPendingCheckoutSession) => {
|
||||
const teamPendingCreation = await prisma.teamPending.findFirstOrThrow({
|
||||
where: {
|
||||
id: pendingTeamId,
|
||||
ownerUserId: userId,
|
||||
},
|
||||
include: {
|
||||
owner: true,
|
||||
},
|
||||
});
|
||||
|
||||
const prices = await getTeamPrices();
|
||||
const priceId = prices[interval].priceId;
|
||||
|
||||
try {
|
||||
const stripeCheckoutSession = await getCheckoutSession({
|
||||
customerId: teamPendingCreation.customerId,
|
||||
priceId,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/teams`,
|
||||
subscriptionMetadata: {
|
||||
pendingTeamId: pendingTeamId.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!stripeCheckoutSession) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR);
|
||||
}
|
||||
|
||||
return stripeCheckoutSession;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
// Absorb all the errors incase Stripe throws something sensitive.
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Something went wrong.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,19 +1,7 @@
|
||||
import {
|
||||
OrganisationGroupType,
|
||||
OrganisationMemberRole,
|
||||
Prisma,
|
||||
TeamMemberRole,
|
||||
} from '@prisma/client';
|
||||
import type Stripe from 'stripe';
|
||||
import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createOrganisationCustomer } from '@documenso/ee/server-only/stripe/create-team-customer';
|
||||
import { getTeamRelatedPrices } from '@documenso/ee/server-only/stripe/get-team-related-prices';
|
||||
import { mapStripeSubscriptionToPrismaUpsertAction } from '@documenso/ee/server-only/stripe/webhook/on-subscription-updated';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import {
|
||||
@ -23,7 +11,6 @@ import {
|
||||
import { TEAM_INTERNAL_GROUPS } from '../../constants/teams';
|
||||
import { buildOrganisationWhereQuery } from '../../utils/organisations';
|
||||
import { generateDefaultTeamSettings } from '../../utils/teams';
|
||||
import { stripe } from '../stripe';
|
||||
|
||||
export type CreateTeamOptions = {
|
||||
/**
|
||||
@ -41,7 +28,7 @@ export type CreateTeamOptions = {
|
||||
*
|
||||
* Used as the URL path, example: https://documenso.com/t/{teamUrl}/settings
|
||||
*/
|
||||
teamUrl: string;
|
||||
teamUrl: string; // Todo: orgs make unique
|
||||
|
||||
/**
|
||||
* ID of the organisation the team belongs to.
|
||||
@ -62,28 +49,13 @@ export type CreateTeamOptions = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export const ZCreateTeamResponseSchema = z.union([
|
||||
z.object({
|
||||
paymentRequired: z.literal(false),
|
||||
}),
|
||||
z.object({
|
||||
paymentRequired: z.literal(true),
|
||||
pendingTeamId: z.number(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type TCreateTeamResponse = z.infer<typeof ZCreateTeamResponseSchema>;
|
||||
|
||||
/**
|
||||
* Create a team or pending team depending on the user's subscription or application's billing settings.
|
||||
*/
|
||||
export const createTeam = async ({
|
||||
userId,
|
||||
teamName,
|
||||
teamUrl,
|
||||
organisationId,
|
||||
inheritMembers,
|
||||
}: CreateTeamOptions): Promise<TCreateTeamResponse> => {
|
||||
}: CreateTeamOptions) => {
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery(
|
||||
organisationId,
|
||||
@ -91,8 +63,9 @@ export const createTeam = async ({
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
),
|
||||
include: {
|
||||
groups: true, // Todo: (orgs)
|
||||
subscriptions: true,
|
||||
groups: true,
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
owner: {
|
||||
select: {
|
||||
id: true,
|
||||
@ -109,6 +82,21 @@ export const createTeam = async ({
|
||||
});
|
||||
}
|
||||
|
||||
// Validate they have enough team slots. 0 means they can create unlimited teams.
|
||||
if (organisation.organisationClaim.teamCount !== 0) {
|
||||
const teamCount = await prisma.team.count({
|
||||
where: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (teamCount >= organisation.organisationClaim.teamCount) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached the maximum number of teams for your plan.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Inherit internal organisation groups to the team.
|
||||
// Organisation Admins/Mangers get assigned as team admins, members get assigned as team members.
|
||||
const internalOrganisationGroups = organisation.groups
|
||||
@ -141,254 +129,46 @@ export const createTeam = async ({
|
||||
.exhaustive(),
|
||||
);
|
||||
|
||||
console.log({
|
||||
internalOrganisationGroups,
|
||||
});
|
||||
|
||||
if (Date.now() > 0) {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const teamSettings = await tx.teamGlobalSettings.create({
|
||||
data: generateDefaultTeamSettings(),
|
||||
});
|
||||
|
||||
const team = await tx.team.create({
|
||||
data: {
|
||||
name: teamName,
|
||||
url: teamUrl,
|
||||
organisationId,
|
||||
teamGlobalSettingsId: teamSettings.id,
|
||||
teamGroups: {
|
||||
createMany: {
|
||||
// Attach the internal organisation groups to the team.
|
||||
data: internalOrganisationGroups,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
teamGroups: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Create the internal team groups.
|
||||
await Promise.all(
|
||||
TEAM_INTERNAL_GROUPS.map(async (teamGroup) =>
|
||||
tx.organisationGroup.create({
|
||||
data: {
|
||||
type: teamGroup.type,
|
||||
organisationRole: LOWEST_ORGANISATION_ROLE,
|
||||
organisationId,
|
||||
teamGroups: {
|
||||
create: {
|
||||
teamId: team.id,
|
||||
teamRole: teamGroup.teamRole,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
paymentRequired: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (Date.now() > 0) {
|
||||
throw new Error('Todo: Orgs');
|
||||
}
|
||||
|
||||
let isPaymentRequired = IS_BILLING_ENABLED();
|
||||
let customerId: string | null = null;
|
||||
|
||||
if (IS_BILLING_ENABLED()) {
|
||||
const teamRelatedPriceIds = await getTeamRelatedPrices().then((prices) =>
|
||||
prices.map((price) => price.id),
|
||||
);
|
||||
|
||||
isPaymentRequired = !subscriptionsContainsActivePlan(
|
||||
organisation.subscriptions,
|
||||
teamRelatedPriceIds, // Todo: (orgs)
|
||||
);
|
||||
|
||||
customerId = await createOrganisationCustomer({
|
||||
name: organisation.owner.name ?? teamName,
|
||||
email: organisation.owner.email,
|
||||
}).then((customer) => customer.id);
|
||||
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
customerId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Create the team directly if no payment is required.
|
||||
if (!isPaymentRequired) {
|
||||
await prisma.team.create({
|
||||
data: {
|
||||
name: teamName,
|
||||
url: teamUrl,
|
||||
organisationId,
|
||||
members: {
|
||||
create: [
|
||||
{
|
||||
userId,
|
||||
role: TeamMemberRole.ADMIN, // Todo: (orgs)
|
||||
},
|
||||
],
|
||||
},
|
||||
teamGlobalSettings: {
|
||||
create: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
paymentRequired: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Create a pending team if payment is required.
|
||||
const pendingTeam = await prisma.$transaction(async (tx) => {
|
||||
const existingTeamWithUrl = await tx.team.findUnique({
|
||||
where: {
|
||||
url: teamUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const existingUserProfileWithUrl = await tx.user.findUnique({
|
||||
where: {
|
||||
url: teamUrl,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUserProfileWithUrl) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'URL already taken.',
|
||||
});
|
||||
}
|
||||
|
||||
if (existingTeamWithUrl) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'Team URL already exists.',
|
||||
});
|
||||
}
|
||||
|
||||
if (!customerId) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Missing customer ID for pending teams.',
|
||||
});
|
||||
}
|
||||
|
||||
return await tx.teamPending.create({
|
||||
data: {
|
||||
name: teamName,
|
||||
url: teamUrl,
|
||||
ownerUserId: user.id,
|
||||
customerId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
paymentRequired: true,
|
||||
pendingTeamId: pendingTeam.id,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
if (!(err instanceof Prisma.PrismaClientKnownRequestError)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const target = z.array(z.string()).safeParse(err.meta?.target);
|
||||
|
||||
if (err.code === 'P2002' && target.success && target.data.includes('url')) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'Team URL already exists.',
|
||||
});
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
export type CreateTeamFromPendingTeamOptions = {
|
||||
pendingTeamId: number;
|
||||
subscription: Stripe.Subscription;
|
||||
};
|
||||
|
||||
export const createTeamFromPendingTeam = async ({
|
||||
pendingTeamId,
|
||||
subscription,
|
||||
}: CreateTeamFromPendingTeamOptions) => {
|
||||
const createdTeam = await prisma.$transaction(async (tx) => {
|
||||
const pendingTeam = await tx.teamPending.findUniqueOrThrow({
|
||||
where: {
|
||||
id: pendingTeamId,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.teamPending.delete({
|
||||
where: {
|
||||
id: pendingTeamId,
|
||||
},
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const teamSettings = await tx.teamGlobalSettings.create({
|
||||
data: generateDefaultTeamSettings(),
|
||||
});
|
||||
|
||||
const team = await tx.team.create({
|
||||
data: {
|
||||
name: pendingTeam.name,
|
||||
url: pendingTeam.url,
|
||||
ownerUserId: pendingTeam.ownerUserId,
|
||||
customerId: pendingTeam.customerId,
|
||||
members: {
|
||||
create: [
|
||||
{
|
||||
userId: pendingTeam.ownerUserId,
|
||||
role: TeamMemberRole.ADMIN,
|
||||
},
|
||||
],
|
||||
name: teamName,
|
||||
url: teamUrl,
|
||||
organisationId,
|
||||
teamGlobalSettingsId: teamSettings.id,
|
||||
teamGroups: {
|
||||
createMany: {
|
||||
// Attach the internal organisation groups to the team.
|
||||
data: internalOrganisationGroups,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await tx.teamGlobalSettings.upsert({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
teamId: team.id,
|
||||
include: {
|
||||
teamGroups: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.subscription.upsert(
|
||||
mapStripeSubscriptionToPrismaUpsertAction(subscription, undefined, team.id),
|
||||
// Create the internal team groups.
|
||||
await Promise.all(
|
||||
TEAM_INTERNAL_GROUPS.map(async (teamGroup) =>
|
||||
tx.organisationGroup.create({
|
||||
data: {
|
||||
type: teamGroup.type,
|
||||
organisationRole: LOWEST_ORGANISATION_ROLE,
|
||||
organisationId,
|
||||
teamGroups: {
|
||||
create: {
|
||||
teamId: team.id,
|
||||
teamRole: teamGroup.teamRole,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return team;
|
||||
});
|
||||
|
||||
// Attach the team ID to the subscription metadata for sanity reasons.
|
||||
await stripe.subscriptions
|
||||
.update(subscription.id, {
|
||||
metadata: {
|
||||
teamId: createdTeam.id.toString(),
|
||||
},
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
// Non-critical error, but we want to log it so we can rectify it.
|
||||
// Todo: Teams - Alert us.
|
||||
});
|
||||
|
||||
return createdTeam;
|
||||
};
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export type DeleteTeamPendingOptions = {
|
||||
userId: number;
|
||||
pendingTeamId: number;
|
||||
};
|
||||
|
||||
export const deleteTeamPending = async ({ userId, pendingTeamId }: DeleteTeamPendingOptions) => {
|
||||
await prisma.teamPending.delete({
|
||||
where: {
|
||||
id: pendingTeamId,
|
||||
ownerUserId: userId,
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -1,8 +1,8 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { OrganisationGlobalSettings } from '@prisma/client';
|
||||
import { OrganisationGroupType, type Team } from '@prisma/client';
|
||||
import { uniqueBy } from 'remeda';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { TeamDeleteEmailTemplate } from '@documenso/email/templates/team-delete';
|
||||
@ -13,8 +13,8 @@ import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams';
|
||||
import { jobs } from '../../jobs/client';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getTeamSettings } from './get-team-settings';
|
||||
|
||||
@ -28,6 +28,11 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['DELETE_TEAM']),
|
||||
include: {
|
||||
organisation: {
|
||||
select: {
|
||||
organisationGlobalSettings: true,
|
||||
},
|
||||
},
|
||||
teamGroups: {
|
||||
include: {
|
||||
organisationGroup: {
|
||||
@ -65,21 +70,19 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
|
||||
teamId,
|
||||
});
|
||||
|
||||
const membersToNotify = uniqueBy(
|
||||
team.teamGroups.flatMap((group) =>
|
||||
group.organisationGroup.organisationGroupMembers.map((member) => ({
|
||||
id: member.organisationMember.user.id,
|
||||
name: member.organisationMember.user.name || '',
|
||||
email: member.organisationMember.user.email,
|
||||
})),
|
||||
),
|
||||
(member) => member.id,
|
||||
);
|
||||
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
// Todo: orgs handle any subs?
|
||||
// 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,
|
||||
@ -96,25 +99,20 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
|
||||
},
|
||||
});
|
||||
|
||||
// const members = team.teamGroups.flatMap((group) =>
|
||||
// group.organisationGroup.organisationMembers.map((member) => ({
|
||||
// id: member.user.id,
|
||||
// name: member.user.name || '',
|
||||
// email: member.user.email,
|
||||
// })),
|
||||
// );
|
||||
|
||||
// await jobs.triggerJob({
|
||||
// name: 'send.team-deleted.email',
|
||||
// payload: {
|
||||
// team: {
|
||||
// name: team.name,
|
||||
// url: team.url,
|
||||
// teamGlobalSettings: team.teamGlobalSettings, // Todo: orgs
|
||||
// },
|
||||
// members,
|
||||
// },
|
||||
// });
|
||||
await jobs.triggerJob({
|
||||
name: 'send.team-deleted.email',
|
||||
payload: {
|
||||
team: {
|
||||
name: team.name,
|
||||
url: team.url,
|
||||
// teamGlobalSettings: {
|
||||
// ...settings,
|
||||
// teamId: team.id,
|
||||
// },
|
||||
},
|
||||
members: membersToNotify,
|
||||
},
|
||||
});
|
||||
},
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
@ -122,14 +120,14 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
|
||||
|
||||
type SendTeamDeleteEmailOptions = {
|
||||
email: string;
|
||||
team: Pick<Team, 'id' | 'url' | 'name'>;
|
||||
settings: Omit<OrganisationGlobalSettings, 'id'>;
|
||||
team: Pick<Team, 'url' | 'name'>;
|
||||
// settings: Omit<OrganisationGlobalSettings, 'id'>;
|
||||
};
|
||||
|
||||
export const sendTeamDeleteEmail = async ({
|
||||
email,
|
||||
team,
|
||||
settings,
|
||||
// settings,
|
||||
}: SendTeamDeleteEmailOptions) => {
|
||||
const template = createElement(TeamDeleteEmailTemplate, {
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
@ -137,9 +135,12 @@ export const sendTeamDeleteEmail = async ({
|
||||
teamUrl: team.url,
|
||||
});
|
||||
|
||||
const branding = teamGlobalSettingsToBranding(settings, team.id);
|
||||
// This is never actually passed on so commenting it out.
|
||||
// const branding = teamGlobalSettingsToBranding(settings, team.id);
|
||||
// const lang = settings.documentLanguage;
|
||||
|
||||
const lang = settings.documentLanguage;
|
||||
const branding = undefined;
|
||||
const lang = undefined;
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang, branding }),
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
import type { Team } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamPendingSchema } from '@documenso/prisma/generated/zod/modelSchema/TeamPendingSchema';
|
||||
|
||||
import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params';
|
||||
|
||||
export interface FindTeamsPendingOptions {
|
||||
userId: number;
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
orderBy?: {
|
||||
column: keyof Team;
|
||||
direction: 'asc' | 'desc';
|
||||
};
|
||||
}
|
||||
|
||||
export const ZFindTeamsPendingResponseSchema = ZFindResultResponse.extend({
|
||||
data: TeamPendingSchema.array(),
|
||||
});
|
||||
|
||||
export type TFindTeamsPendingResponse = z.infer<typeof ZFindTeamsPendingResponseSchema>;
|
||||
|
||||
export const findTeamsPending = async ({
|
||||
userId,
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
orderBy,
|
||||
}: FindTeamsPendingOptions): Promise<TFindTeamsPendingResponse> => {
|
||||
const orderByColumn = orderBy?.column ?? 'name';
|
||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
||||
|
||||
const whereClause: Prisma.TeamPendingWhereInput = {
|
||||
ownerUserId: userId,
|
||||
};
|
||||
|
||||
if (query && query.length > 0) {
|
||||
whereClause.name = {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
};
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.teamPending.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
[orderByColumn]: orderByDirection,
|
||||
},
|
||||
}),
|
||||
prisma.teamPending.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
};
|
||||
@ -19,7 +19,7 @@ export type UpdateTeamOptions = {
|
||||
export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions): Promise<void> => {
|
||||
try {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const foundPendingTeamWithUrl = await tx.teamPending.findFirst({
|
||||
const foundTeamWithUrl = await tx.team.findFirst({
|
||||
where: {
|
||||
url: data.url,
|
||||
},
|
||||
@ -31,21 +31,19 @@ export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions): P
|
||||
},
|
||||
});
|
||||
|
||||
if (foundPendingTeamWithUrl || foundOrganisationWithUrl) {
|
||||
if (foundTeamWithUrl || foundOrganisationWithUrl) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'Team URL already exists.',
|
||||
});
|
||||
}
|
||||
|
||||
const team = await tx.team.update({
|
||||
return await tx.team.update({
|
||||
where: buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']),
|
||||
data: {
|
||||
url: data.url,
|
||||
name: data.name,
|
||||
},
|
||||
});
|
||||
|
||||
return team;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
Reference in New Issue
Block a user