mirror of
https://github.com/documenso/documenso.git
synced 2025-11-21 04:01:45 +10:00
feat: billing
This commit is contained in:
50
packages/trpc/server/admin-router/create-stripe-customer.ts
Normal file
50
packages/trpc/server/admin-router/create-stripe-customer.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateStripeCustomerRequestSchema,
|
||||
ZCreateStripeCustomerResponseSchema,
|
||||
} from './create-stripe-customer.types';
|
||||
|
||||
export const createStripeCustomerRoute = adminProcedure
|
||||
.input(ZCreateStripeCustomerRequestSchema)
|
||||
.output(ZCreateStripeCustomerResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
include: {
|
||||
owner: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const stripeCustomer = await createCustomer({
|
||||
name: organisation.name,
|
||||
email: organisation.owner.email,
|
||||
});
|
||||
|
||||
await tx.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
customerId: stripeCustomer.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateStripeCustomerRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to attach the customer to'),
|
||||
});
|
||||
|
||||
export const ZCreateStripeCustomerResponseSchema = z.void();
|
||||
|
||||
export type TCreateStripeCustomerRequest = z.infer<typeof ZCreateStripeCustomerRequestSchema>;
|
||||
@ -0,0 +1,23 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZCreateSubscriptionClaimRequestSchema,
|
||||
ZCreateSubscriptionClaimResponseSchema,
|
||||
} from './create-subscription-claim.types';
|
||||
|
||||
export const createSubscriptionClaimRoute = adminProcedure
|
||||
.input(ZCreateSubscriptionClaimRequestSchema)
|
||||
.output(ZCreateSubscriptionClaimResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const { name, teamCount, memberCount, flags } = input;
|
||||
|
||||
await prisma.subscriptionClaim.create({
|
||||
data: {
|
||||
name,
|
||||
teamCount,
|
||||
memberCount,
|
||||
flags,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZClaimFlagsSchema } from '@documenso/lib/types/subscription';
|
||||
|
||||
export const ZCreateSubscriptionClaimRequestSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
teamCount: z.number().int().min(0),
|
||||
memberCount: z.number().int().min(0),
|
||||
flags: ZClaimFlagsSchema,
|
||||
});
|
||||
|
||||
export const ZCreateSubscriptionClaimResponseSchema = z.void();
|
||||
|
||||
export type TCreateSubscriptionClaimRequest = z.infer<typeof ZCreateSubscriptionClaimRequestSchema>;
|
||||
@ -0,0 +1,37 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteSubscriptionClaimRequestSchema,
|
||||
ZDeleteSubscriptionClaimResponseSchema,
|
||||
} from './delete-subscription-claim.types';
|
||||
|
||||
export const deleteSubscriptionClaimRoute = adminProcedure
|
||||
.input(ZDeleteSubscriptionClaimRequestSchema)
|
||||
.output(ZDeleteSubscriptionClaimResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const { id } = input;
|
||||
|
||||
const existingClaim = await prisma.subscriptionClaim.findFirst({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingClaim) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, { message: 'Subscription claim not found' });
|
||||
}
|
||||
|
||||
if (existingClaim.locked) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Cannot delete locked subscription claim',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.subscriptionClaim.delete({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteSubscriptionClaimRequestSchema = z.object({
|
||||
id: z.string().cuid(),
|
||||
});
|
||||
|
||||
export const ZDeleteSubscriptionClaimResponseSchema = z.void();
|
||||
|
||||
export type TDeleteSubscriptionClaimRequest = z.infer<typeof ZDeleteSubscriptionClaimRequestSchema>;
|
||||
128
packages/trpc/server/admin-router/find-admin-organisations.ts
Normal file
128
packages/trpc/server/admin-router/find-admin-organisations.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindAdminOrganisationsRequestSchema,
|
||||
ZFindAdminOrganisationsResponseSchema,
|
||||
} from './find-admin-organisations.types';
|
||||
|
||||
export const findAdminOrganisationsRoute = adminProcedure
|
||||
.input(ZFindAdminOrganisationsRequestSchema)
|
||||
.output(ZFindAdminOrganisationsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { query, page, perPage } = input;
|
||||
|
||||
return await findAdminOrganisations({
|
||||
query,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
});
|
||||
|
||||
type FindAdminOrganisationsOptions = {
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const findAdminOrganisations = async ({
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: FindAdminOrganisationsOptions) => {
|
||||
let whereClause: Prisma.OrganisationWhereInput = {};
|
||||
|
||||
if (query) {
|
||||
whereClause = {
|
||||
OR: [
|
||||
{
|
||||
id: {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
owner: {
|
||||
email: {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
customerId: {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (query && query.startsWith('claim:')) {
|
||||
whereClause = {
|
||||
organisationClaim: {
|
||||
originalSubscriptionClaimId: {
|
||||
contains: query.slice(6),
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (query && query.startsWith('org_')) {
|
||||
whereClause = {
|
||||
url: {
|
||||
equals: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.organisation.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
url: true,
|
||||
customerId: true,
|
||||
owner: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
subscription: true,
|
||||
},
|
||||
}),
|
||||
prisma.organisation.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import OrganisationSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationSchema';
|
||||
import SubscriptionSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema';
|
||||
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||
|
||||
export const ZFindAdminOrganisationsRequestSchema = ZFindSearchParamsSchema;
|
||||
|
||||
export const ZFindAdminOrganisationsResponseSchema = ZFindResultResponse.extend({
|
||||
data: OrganisationSchema.pick({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
url: true,
|
||||
customerId: true,
|
||||
})
|
||||
.extend({
|
||||
owner: UserSchema.pick({
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
}),
|
||||
subscription: SubscriptionSchema.pick({
|
||||
status: true,
|
||||
id: true,
|
||||
planId: true,
|
||||
priceId: true,
|
||||
periodEnd: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
cancelAtPeriodEnd: true,
|
||||
}).nullable(),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type TFindAdminOrganisationsResponse = z.infer<typeof ZFindAdminOrganisationsResponseSchema>;
|
||||
@ -0,0 +1,76 @@
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type SubscriptionClaimSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionClaimSchema';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindSubscriptionClaimsRequestSchema,
|
||||
ZFindSubscriptionClaimsResponseSchema,
|
||||
} from './find-subscription-claims.types';
|
||||
|
||||
export const findSubscriptionClaimsRoute = adminProcedure
|
||||
.input(ZFindSubscriptionClaimsRequestSchema)
|
||||
.output(ZFindSubscriptionClaimsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { query, page, perPage } = input;
|
||||
|
||||
return await findSubscriptionClaims({ query, page, perPage });
|
||||
});
|
||||
|
||||
type FindSubscriptionClaimsOptions = {
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const findSubscriptionClaims = async ({
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 50,
|
||||
}: FindSubscriptionClaimsOptions) => {
|
||||
let whereClause: Prisma.SubscriptionClaimWhereInput = {};
|
||||
|
||||
if (query) {
|
||||
whereClause = {
|
||||
OR: [
|
||||
{
|
||||
id: {
|
||||
contains: query,
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
contains: query,
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.subscriptionClaim.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
}),
|
||||
prisma.subscriptionClaim.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<z.infer<typeof SubscriptionClaimSchema>[]>;
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import SubscriptionClaimSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionClaimSchema';
|
||||
|
||||
export const ZFindSubscriptionClaimsRequestSchema = ZFindSearchParamsSchema.extend({});
|
||||
|
||||
export const ZFindSubscriptionClaimsResponseSchema = ZFindResultResponse.extend({
|
||||
data: SubscriptionClaimSchema.pick({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
teamCount: true,
|
||||
memberCount: true,
|
||||
locked: true,
|
||||
flags: true,
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export type TFindSubscriptionClaimsRequest = z.infer<typeof ZFindSubscriptionClaimsRequestSchema>;
|
||||
export type TFindSubscriptionClaimsResponse = z.infer<typeof ZFindSubscriptionClaimsResponseSchema>;
|
||||
56
packages/trpc/server/admin-router/get-admin-organisation.ts
Normal file
56
packages/trpc/server/admin-router/get-admin-organisation.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZGetAdminOrganisationRequestSchema,
|
||||
ZGetAdminOrganisationResponseSchema,
|
||||
} from './get-admin-organisation.types';
|
||||
|
||||
export const getAdminOrganisationRoute = adminProcedure
|
||||
.input(ZGetAdminOrganisationRequestSchema)
|
||||
.output(ZGetAdminOrganisationResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
return await getAdminOrganisation({
|
||||
organisationId,
|
||||
});
|
||||
});
|
||||
|
||||
type GetOrganisationOptions = {
|
||||
organisationId: string;
|
||||
};
|
||||
|
||||
export const getAdminOrganisation = async ({ organisationId }: GetOrganisationOptions) => {
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
include: {
|
||||
organisationClaim: true,
|
||||
organisationGlobalSettings: true,
|
||||
teams: true,
|
||||
members: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
subscription: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Organisation not found',
|
||||
});
|
||||
}
|
||||
|
||||
return organisation;
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZOrganisationSchema } from '@documenso/lib/types/organisation';
|
||||
import OrganisationClaimSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationClaimSchema';
|
||||
import OrganisationGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationGlobalSettingsSchema';
|
||||
import OrganisationMemberSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationMemberSchema';
|
||||
import SubscriptionSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema';
|
||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||
|
||||
export const ZGetAdminOrganisationRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
});
|
||||
|
||||
export const ZGetAdminOrganisationResponseSchema = ZOrganisationSchema.extend({
|
||||
organisationGlobalSettings: OrganisationGlobalSettingsSchema,
|
||||
teams: z.array(
|
||||
TeamSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
createdAt: true,
|
||||
avatarImageId: true,
|
||||
organisationId: true,
|
||||
}),
|
||||
),
|
||||
members: OrganisationMemberSchema.extend({
|
||||
user: UserSchema.pick({
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
}),
|
||||
}).array(),
|
||||
subscription: SubscriptionSchema.nullable(),
|
||||
organisationClaim: OrganisationClaimSchema,
|
||||
});
|
||||
|
||||
export type TGetAdminOrganisationResponse = z.infer<typeof ZGetAdminOrganisationResponseSchema>;
|
||||
@ -14,6 +14,12 @@ import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
|
||||
import { adminProcedure, router } from '../trpc';
|
||||
import { createStripeCustomerRoute } from './create-stripe-customer';
|
||||
import { createSubscriptionClaimRoute } from './create-subscription-claim';
|
||||
import { deleteSubscriptionClaimRoute } from './delete-subscription-claim';
|
||||
import { findAdminOrganisationsRoute } from './find-admin-organisations';
|
||||
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
|
||||
import { getAdminOrganisationRoute } from './get-admin-organisation';
|
||||
import {
|
||||
ZAdminDeleteDocumentMutationSchema,
|
||||
ZAdminDeleteUserMutationSchema,
|
||||
@ -25,8 +31,30 @@ import {
|
||||
ZAdminUpdateRecipientMutationSchema,
|
||||
ZAdminUpdateSiteSettingMutationSchema,
|
||||
} from './schema';
|
||||
import { updateAdminOrganisationRoute } from './update-admin-organisation';
|
||||
import { updateSubscriptionClaimRoute } from './update-subscription-claim';
|
||||
|
||||
export const adminRouter = router({
|
||||
// Todo: orgs Ensure all procedures are admin within this route.
|
||||
// Todo: orgs Ensure all procedures are admin within this route.
|
||||
// Todo: orgs Ensure all procedures are admin within this route.
|
||||
// Todo: orgs Ensure all procedures are admin within this route.
|
||||
organisation: {
|
||||
find: findAdminOrganisationsRoute,
|
||||
get: getAdminOrganisationRoute,
|
||||
update: updateAdminOrganisationRoute,
|
||||
},
|
||||
claims: {
|
||||
find: findSubscriptionClaimsRoute,
|
||||
create: createSubscriptionClaimRoute,
|
||||
update: updateSubscriptionClaimRoute,
|
||||
delete: deleteSubscriptionClaimRoute,
|
||||
},
|
||||
stripe: {
|
||||
createCustomer: createStripeCustomerRoute,
|
||||
},
|
||||
|
||||
// Todo: migrate old routes
|
||||
findDocuments: adminProcedure.input(ZAdminFindDocumentsQuerySchema).query(async ({ input }) => {
|
||||
const { query, page, perPage } = input;
|
||||
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateAdminOrganisationRequestSchema,
|
||||
ZUpdateAdminOrganisationResponseSchema,
|
||||
} from './update-admin-organisation.types';
|
||||
|
||||
export const updateAdminOrganisationRoute = adminProcedure
|
||||
.input(ZUpdateAdminOrganisationRequestSchema)
|
||||
.output(ZUpdateAdminOrganisationResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const { organisationId, data } = input;
|
||||
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
include: {
|
||||
organisationClaim: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const { name, url, customerId, claims, originalSubscriptionClaimId } = data;
|
||||
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
name,
|
||||
url,
|
||||
customerId: customerId === undefined ? null : customerId,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.organisationClaim.update({
|
||||
where: {
|
||||
id: organisation.organisationClaimId,
|
||||
},
|
||||
data: {
|
||||
...claims,
|
||||
originalSubscriptionClaimId,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,24 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZOrganisationNameSchema } from '../organisation-router/create-organisation.types';
|
||||
import { ZTeamUrlSchema } from '../team-router/schema';
|
||||
import { ZCreateSubscriptionClaimRequestSchema } from './create-subscription-claim.types';
|
||||
|
||||
export const ZUpdateAdminOrganisationRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
data: z.object({
|
||||
name: ZOrganisationNameSchema.optional(),
|
||||
url: ZTeamUrlSchema.optional(),
|
||||
claims: ZCreateSubscriptionClaimRequestSchema.pick({
|
||||
teamCount: true,
|
||||
memberCount: true,
|
||||
flags: true,
|
||||
}).optional(),
|
||||
customerId: z.string().optional(),
|
||||
originalSubscriptionClaimId: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ZUpdateAdminOrganisationResponseSchema = z.void();
|
||||
|
||||
export type TUpdateAdminOrganisationRequest = z.infer<typeof ZUpdateAdminOrganisationRequestSchema>;
|
||||
@ -0,0 +1,56 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { TClaimFlags } from '@documenso/lib/types/subscription';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateSubscriptionClaimRequestSchema,
|
||||
ZUpdateSubscriptionClaimResponseSchema,
|
||||
} from './update-subscription-claim.types';
|
||||
|
||||
export const updateSubscriptionClaimRoute = adminProcedure
|
||||
.input(ZUpdateSubscriptionClaimRequestSchema)
|
||||
.output(ZUpdateSubscriptionClaimResponseSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
const { id, data } = input;
|
||||
|
||||
const existingClaim = await prisma.subscriptionClaim.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existingClaim) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, { message: 'Subscription claim not found' });
|
||||
}
|
||||
|
||||
const newlyEnabledFlags = getNewTruthyKeys(existingClaim.flags, data.flags);
|
||||
|
||||
console.log({
|
||||
newlyEnabledFlags,
|
||||
});
|
||||
|
||||
if (newlyEnabledFlags.length > 0) {
|
||||
// Todo: orgs backport claims
|
||||
}
|
||||
|
||||
await prisma.subscriptionClaim.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data,
|
||||
});
|
||||
});
|
||||
|
||||
type BoolMap = Record<string, boolean | undefined>;
|
||||
|
||||
/**
|
||||
* Get the new truthy keys from the existing flags and the new flags.
|
||||
*
|
||||
* @param a - The existing flags.
|
||||
* @param b - The new flags.
|
||||
* @returns The new truthy keys.
|
||||
*/
|
||||
function getNewTruthyKeys(a: BoolMap, b: BoolMap): (keyof TClaimFlags)[] {
|
||||
return Object.entries(b)
|
||||
.filter(([key, value]) => value && !a[key])
|
||||
.map(([key]) => key as keyof TClaimFlags);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZCreateSubscriptionClaimRequestSchema } from './create-subscription-claim.types';
|
||||
|
||||
export const ZUpdateSubscriptionClaimRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
data: ZCreateSubscriptionClaimRequestSchema,
|
||||
});
|
||||
|
||||
export const ZUpdateSubscriptionClaimResponseSchema = z.void();
|
||||
|
||||
export type TUpdateSubscriptionClaimRequest = z.infer<typeof ZUpdateSubscriptionClaimRequestSchema>;
|
||||
81
packages/trpc/server/billing/create-subscription.ts
Normal file
81
packages/trpc/server/billing/create-subscription.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { createCheckoutSession } from '@documenso/ee/server-only/stripe/create-checkout-session';
|
||||
import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer';
|
||||
import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZCreateSubscriptionRequestSchema } from './create-subscription.types';
|
||||
|
||||
export const createSubscriptionRoute = authenticatedProcedure
|
||||
.input(ZCreateSubscriptionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId, priceId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Billing is not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery(
|
||||
organisationId,
|
||||
userId,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_BILLING'],
|
||||
),
|
||||
include: {
|
||||
subscription: true,
|
||||
owner: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
let customerId = organisation.customerId;
|
||||
|
||||
if (!customerId) {
|
||||
const customer = await createCustomer({
|
||||
name: organisation.name,
|
||||
email: organisation.owner.email,
|
||||
});
|
||||
|
||||
customerId = customer.id;
|
||||
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
customerId: customer.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const redirectUrl = await createCheckoutSession({
|
||||
customerId,
|
||||
priceId,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/org/${organisation.url}/settings/billing`,
|
||||
});
|
||||
|
||||
if (!redirectUrl) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
message: 'Failed to create checkout session',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
redirectUrl,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateSubscriptionRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to create the subscription for'),
|
||||
priceId: z.string().describe('The price to create the subscription for'),
|
||||
});
|
||||
58
packages/trpc/server/billing/get-invoices.ts
Normal file
58
packages/trpc/server/billing/get-invoices.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { getInvoices } from '@documenso/ee/server-only/stripe/get-invoices';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZGetInvoicesRequestSchema } from './get-invoices.types';
|
||||
|
||||
export const getInvoicesRoute = authenticatedProcedure
|
||||
.input(ZGetInvoicesRequestSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Billing is not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery(
|
||||
organisationId,
|
||||
userId,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
),
|
||||
include: {
|
||||
subscription: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not authorized to access this organisation',
|
||||
});
|
||||
}
|
||||
|
||||
if (!organisation.subscription?.customerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const invoices = await getInvoices({
|
||||
customerId: organisation.subscription?.customerId,
|
||||
});
|
||||
|
||||
return invoices.data.map((invoice) => ({
|
||||
id: invoice.id,
|
||||
status: invoice.status,
|
||||
created: invoice.created,
|
||||
currency: invoice.currency,
|
||||
total: invoice.total,
|
||||
hosted_invoice_url: invoice.hosted_invoice_url,
|
||||
invoice_pdf: invoice.invoice_pdf,
|
||||
}));
|
||||
});
|
||||
5
packages/trpc/server/billing/get-invoices.types.ts
Normal file
5
packages/trpc/server/billing/get-invoices.types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetInvoicesRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to get the invoices for'),
|
||||
});
|
||||
31
packages/trpc/server/billing/get-plans.ts
Normal file
31
packages/trpc/server/billing/get-plans.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { getInternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
|
||||
export const getPlansRoute = authenticatedProcedure.query(async ({ ctx }) => {
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const plans = await getInternalClaimPlans();
|
||||
|
||||
let canCreateFreeOrganisation = false;
|
||||
|
||||
if (IS_BILLING_ENABLED()) {
|
||||
const numberOfFreeOrganisations = await prisma.organisation.count({
|
||||
where: {
|
||||
ownerUserId: userId,
|
||||
subscription: {
|
||||
is: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
canCreateFreeOrganisation = numberOfFreeOrganisations === 0;
|
||||
}
|
||||
|
||||
return {
|
||||
plans,
|
||||
canCreateFreeOrganisation,
|
||||
};
|
||||
});
|
||||
34
packages/trpc/server/billing/get-subscription.ts
Normal file
34
packages/trpc/server/billing/get-subscription.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { getInternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans';
|
||||
import { getSubscription } from '@documenso/ee/server-only/stripe/get-subscription';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZGetSubscriptionRequestSchema } from './get-subscription.types';
|
||||
|
||||
export const getSubscriptionRoute = authenticatedProcedure
|
||||
.input(ZGetSubscriptionRequestSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Billing is not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const [subscription, plans] = await Promise.all([
|
||||
getSubscription({
|
||||
organisationId,
|
||||
userId,
|
||||
}),
|
||||
getInternalClaimPlans(),
|
||||
]);
|
||||
|
||||
return {
|
||||
subscription,
|
||||
plans,
|
||||
};
|
||||
});
|
||||
5
packages/trpc/server/billing/get-subscription.types.ts
Normal file
5
packages/trpc/server/billing/get-subscription.types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetSubscriptionRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to get the subscription for'),
|
||||
});
|
||||
74
packages/trpc/server/billing/manage-subscription.ts
Normal file
74
packages/trpc/server/billing/manage-subscription.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer';
|
||||
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
|
||||
import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import { ZManageSubscriptionRequestSchema } from './manage-subscription.types';
|
||||
|
||||
export const manageSubscriptionRoute = authenticatedProcedure
|
||||
.input(ZManageSubscriptionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
if (!IS_BILLING_ENABLED()) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Billing is not enabled',
|
||||
});
|
||||
}
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery(
|
||||
organisationId,
|
||||
userId,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_BILLING'],
|
||||
),
|
||||
include: {
|
||||
subscription: true,
|
||||
owner: {
|
||||
select: {
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
let customerId = organisation.customerId;
|
||||
|
||||
if (!customerId) {
|
||||
const customer = await createCustomer({
|
||||
name: organisation.name,
|
||||
email: organisation.owner.email,
|
||||
});
|
||||
|
||||
customerId = customer.id;
|
||||
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
customerId: customer.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const redirectUrl = await getPortalSession({
|
||||
customerId,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/org/${organisation.url}/settings/billing`,
|
||||
});
|
||||
|
||||
return {
|
||||
redirectUrl,
|
||||
};
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZManageSubscriptionRequestSchema = z.object({
|
||||
organisationId: z.string().describe('The organisation to manage the subscription for'),
|
||||
});
|
||||
20
packages/trpc/server/billing/router.ts
Normal file
20
packages/trpc/server/billing/router.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { router } from '../trpc';
|
||||
import { createSubscriptionRoute } from './create-subscription';
|
||||
import { getInvoicesRoute } from './get-invoices';
|
||||
import { getPlansRoute } from './get-plans';
|
||||
import { getSubscriptionRoute } from './get-subscription';
|
||||
import { manageSubscriptionRoute } from './manage-subscription';
|
||||
|
||||
export const billingRouter = router({
|
||||
plans: {
|
||||
get: getPlansRoute,
|
||||
},
|
||||
subscription: {
|
||||
get: getSubscriptionRoute,
|
||||
create: createSubscriptionRoute,
|
||||
manage: manageSubscriptionRoute,
|
||||
},
|
||||
invoices: {
|
||||
get: getInvoicesRoute,
|
||||
},
|
||||
});
|
||||
@ -238,7 +238,7 @@ export const documentRouter = router({
|
||||
.input(ZCreateDocumentV2RequestSchema)
|
||||
.output(ZCreateDocumentV2ResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { teamId, user } = ctx;
|
||||
|
||||
const {
|
||||
title,
|
||||
@ -250,7 +250,7 @@ export const documentRouter = router({
|
||||
meta,
|
||||
} = input;
|
||||
|
||||
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
@ -307,10 +307,10 @@ export const documentRouter = router({
|
||||
// })
|
||||
.input(ZCreateDocumentRequestSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { teamId } = ctx;
|
||||
const { teamId, user } = ctx;
|
||||
const { title, documentDataId, timezone } = input;
|
||||
|
||||
const { remaining } = await getServerLimits({ email: ctx.user.email, teamId });
|
||||
const { remaining } = await getServerLimits({ userId: user.id, teamId });
|
||||
|
||||
if (remaining.documents <= 0) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
@ -320,7 +320,7 @@ export const documentRouter = router({
|
||||
}
|
||||
|
||||
return await createDocument({
|
||||
userId: ctx.user.id,
|
||||
userId: user.id,
|
||||
teamId,
|
||||
title,
|
||||
documentDataId,
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { createCheckoutSession } from '@documenso/ee/server-only/stripe/create-checkout-session';
|
||||
import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer';
|
||||
import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createOrganisation } from '@documenso/lib/server-only/organisation/create-organisation';
|
||||
import { generateStripeOrganisationCreateMetadata } from '@documenso/lib/utils/billing';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@ -11,12 +17,60 @@ export const createOrganisationRoute = authenticatedProcedure
|
||||
.input(ZCreateOrganisationRequestSchema)
|
||||
.output(ZCreateOrganisationResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { name, url } = input;
|
||||
const { name, priceId } = input;
|
||||
const { user } = ctx;
|
||||
|
||||
// Todo: orgs
|
||||
// Todo: Check if user has reached limit.
|
||||
if (IS_BILLING_ENABLED() && !priceId) {
|
||||
const userOrganisations = await prisma.organisation.findMany({
|
||||
where: {
|
||||
ownerUserId: user.id,
|
||||
subscription: {
|
||||
is: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (userOrganisations.length >= 1) {
|
||||
throw new AppError(AppErrorCode.LIMIT_EXCEEDED, {
|
||||
message: 'You have reached the maximum number of free organisations.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create checkout session for payment.
|
||||
if (IS_BILLING_ENABLED() && priceId) {
|
||||
// if (!claimId) {
|
||||
// throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
// message: 'Claim ID is required',
|
||||
// });
|
||||
// }
|
||||
|
||||
const customer = await createCustomer({
|
||||
email: user.email,
|
||||
name: user.name || user.email,
|
||||
});
|
||||
|
||||
const checkoutUrl = await createCheckoutSession({
|
||||
priceId,
|
||||
customerId: customer.id,
|
||||
returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/organisations`,
|
||||
subscriptionMetadata: generateStripeOrganisationCreateMetadata(name, user.id),
|
||||
});
|
||||
|
||||
return {
|
||||
paymentRequired: true,
|
||||
checkoutUrl,
|
||||
};
|
||||
}
|
||||
|
||||
await createOrganisation({
|
||||
userId: user.id,
|
||||
name,
|
||||
url,
|
||||
});
|
||||
|
||||
return {
|
||||
paymentRequired: false,
|
||||
};
|
||||
});
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZTeamUrlSchema } from '../team-router/schema';
|
||||
|
||||
// export const createOrganisationMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
@ -19,7 +17,17 @@ export const ZOrganisationNameSchema = z
|
||||
|
||||
export const ZCreateOrganisationRequestSchema = z.object({
|
||||
name: ZOrganisationNameSchema,
|
||||
url: ZTeamUrlSchema,
|
||||
priceId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ZCreateOrganisationResponseSchema = z.void();
|
||||
export const ZCreateOrganisationResponseSchema = z.union([
|
||||
z.object({
|
||||
paymentRequired: z.literal(false),
|
||||
}),
|
||||
z.object({
|
||||
paymentRequired: z.literal(true),
|
||||
checkoutUrl: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type TCreateOrganisationResponse = z.infer<typeof ZCreateOrganisationResponseSchema>;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { validateIfSubscriptionIsRequired } from '@documenso/lib/utils/billing';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@ -23,12 +25,42 @@ export const deleteOrganisationMemberInvitesRoute = authenticatedProcedure
|
||||
userId,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
),
|
||||
include: {
|
||||
organisationClaim: true,
|
||||
subscription: true,
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
invites: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const { organisationClaim } = organisation;
|
||||
|
||||
const subscription = validateIfSubscriptionIsRequired(organisation.subscription);
|
||||
|
||||
const numberOfCurrentMembers = organisation.members.length;
|
||||
const numberOfCurrentInvites = organisation.invites.length;
|
||||
const totalMemberCountWithInvites = numberOfCurrentMembers + numberOfCurrentInvites - 1;
|
||||
|
||||
if (subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(
|
||||
subscription,
|
||||
organisationClaim,
|
||||
totalMemberCountWithInvites,
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.organisationMemberInvite.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { validateIfSubscriptionIsRequired } from '@documenso/lib/utils/billing';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationMemberInviteStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
@ -30,76 +33,73 @@ type DeleteOrganisationMembersProps = {
|
||||
organisationMemberIds: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes multiple organisation members.
|
||||
*
|
||||
* This logic is also used to leave a team (hence strange logic).
|
||||
*/
|
||||
export const deleteOrganisationMembers = async ({
|
||||
userId,
|
||||
organisationId,
|
||||
organisationMemberIds,
|
||||
}: DeleteOrganisationMembersProps) => {
|
||||
const membersToDelete = await prisma.organisationMember.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: organisationMemberIds,
|
||||
},
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery(
|
||||
organisationId,
|
||||
userId,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
),
|
||||
include: {
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
invites: {
|
||||
where: {
|
||||
status: OrganisationMemberInviteStatus.PENDING,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Prevent the user from deleting other users if they do not have permission.
|
||||
if (membersToDelete.some((member) => member.userId !== userId)) {
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery(
|
||||
organisationId,
|
||||
userId,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
),
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Todo: Orgs - Handle seats.
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
await tx.organisationMember.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: organisationMemberIds,
|
||||
},
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
const { organisationClaim } = organisation;
|
||||
|
||||
// Todo: orgs handle removing groups
|
||||
|
||||
// if (IS_BILLING_ENABLED() && team.subscription) {
|
||||
// const numberOfSeats = await tx.teamMember.count({
|
||||
// where: {
|
||||
// teamId,
|
||||
// },
|
||||
// });
|
||||
|
||||
// await updateSubscriptionItemQuantity({
|
||||
// priceId: team.subscription.priceId,
|
||||
// subscriptionId: team.subscription.planId,
|
||||
// quantity: numberOfSeats,
|
||||
// });
|
||||
// }
|
||||
|
||||
// await jobs.triggerJob({
|
||||
// name: 'send.team-member-left.email',
|
||||
// payload: {
|
||||
// teamId,
|
||||
// memberUserId: leavingUser.id,
|
||||
// },
|
||||
// });
|
||||
},
|
||||
{ timeout: 30_000 },
|
||||
const membersToDelete = organisation.members.filter((member) =>
|
||||
organisationMemberIds.includes(member.id),
|
||||
);
|
||||
|
||||
const subscription = validateIfSubscriptionIsRequired(organisation.subscription);
|
||||
|
||||
const inviteCount = organisation.invites.length;
|
||||
const newMemberCount = organisation.members.length + inviteCount - membersToDelete.length;
|
||||
|
||||
if (subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(subscription, organisationClaim, newMemberCount);
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.organisationMember.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: organisationMemberIds,
|
||||
},
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
// Todo: orgs
|
||||
// await jobs.triggerJob({
|
||||
// name: 'send.team-member-left.email',
|
||||
// payload: {
|
||||
// teamId,
|
||||
// memberUserId: leavingUser.id,
|
||||
// },
|
||||
// });
|
||||
});
|
||||
};
|
||||
|
||||
@ -25,6 +25,7 @@ export const getOrganisationMemberInvitesRoute = authenticatedProcedure
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
avatarImageId: true,
|
||||
},
|
||||
},
|
||||
|
||||
@ -19,6 +19,7 @@ export const ZGetOrganisationMemberInvitesResponseSchema = OrganisationMemberInv
|
||||
organisation: OrganisationSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
avatarImageId: true,
|
||||
}),
|
||||
})
|
||||
|
||||
@ -29,6 +29,7 @@ export const getOrganisationSession = async ({
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organisationClaim: true,
|
||||
groups: {
|
||||
where: {
|
||||
organisationGroupMembers: {
|
||||
|
||||
@ -44,6 +44,8 @@ export const getOrganisation = async ({
|
||||
},
|
||||
include: {
|
||||
organisationGlobalSettings: true,
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
teams: {
|
||||
where: {
|
||||
teamGroups: {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZOrganisationSchema } from '@documenso/lib/types/organisation';
|
||||
import OrganisationClaimSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationClaimSchema';
|
||||
import OrganisationGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationGlobalSettingsSchema';
|
||||
import SubscriptionSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema';
|
||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
|
||||
// export const getOrganisationMeta: TrpcOpenApiMeta = {
|
||||
@ -20,6 +22,8 @@ export const ZGetOrganisationRequestSchema = z.object({
|
||||
|
||||
export const ZGetOrganisationResponseSchema = ZOrganisationSchema.extend({
|
||||
organisationGlobalSettings: OrganisationGlobalSettingsSchema,
|
||||
organisationClaim: OrganisationClaimSchema,
|
||||
subscription: SubscriptionSchema.nullable(),
|
||||
teams: z.array(
|
||||
TeamSchema.pick({
|
||||
id: true,
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { validateIfSubscriptionIsRequired } from '@documenso/lib/utils/billing';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationMemberInviteStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZLeaveOrganisationRequestSchema,
|
||||
ZLeaveOrganisationResponseSchema,
|
||||
} from './leave-organisation.types';
|
||||
|
||||
export const leaveOrganisationRoute = authenticatedProcedure
|
||||
.input(ZLeaveOrganisationRequestSchema)
|
||||
.output(ZLeaveOrganisationResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId } = input;
|
||||
|
||||
const userId = ctx.user.id;
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery(organisationId, userId),
|
||||
include: {
|
||||
organisationClaim: true,
|
||||
subscription: true,
|
||||
invites: {
|
||||
where: {
|
||||
status: OrganisationMemberInviteStatus.PENDING,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const { organisationClaim } = organisation;
|
||||
|
||||
const subscription = validateIfSubscriptionIsRequired(organisation.subscription);
|
||||
|
||||
const inviteCount = organisation.invites.length;
|
||||
const newMemberCount = organisation.members.length + inviteCount - 1;
|
||||
|
||||
if (subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(subscription, organisationClaim, newMemberCount);
|
||||
}
|
||||
|
||||
await prisma.organisationMember.delete({
|
||||
where: {
|
||||
userId_organisationId: {
|
||||
userId,
|
||||
organisationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.organisation-member-left.email',
|
||||
payload: {
|
||||
organisationId: organisation.id,
|
||||
memberUserId: userId,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZLeaveOrganisationRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
});
|
||||
|
||||
export const ZLeaveOrganisationResponseSchema = z.void();
|
||||
@ -16,6 +16,7 @@ import { getOrganisationRoute } from './get-organisation';
|
||||
import { getOrganisationMemberInvitesRoute } from './get-organisation-member-invites';
|
||||
import { getOrganisationSessionRoute } from './get-organisation-session';
|
||||
import { getOrganisationsRoute } from './get-organisations';
|
||||
import { leaveOrganisationRoute } from './leave-organisation';
|
||||
import { resendOrganisationMemberInviteRoute } from './resend-organisation-member-invite';
|
||||
import { updateOrganisationRoute } from './update-organisation';
|
||||
import { updateOrganisationGroupRoute } from './update-organisation-group';
|
||||
@ -28,6 +29,7 @@ export const organisationRouter = router({
|
||||
create: createOrganisationRoute,
|
||||
update: updateOrganisationRoute,
|
||||
delete: deleteOrganisationRoute,
|
||||
leave: leaveOrganisationRoute,
|
||||
member: {
|
||||
find: findOrganisationMembersRoute,
|
||||
update: updateOrganisationMemberRoute,
|
||||
|
||||
@ -39,7 +39,7 @@ export const updateOrganisationRoute = authenticatedProcedure
|
||||
},
|
||||
data: {
|
||||
name: data.name,
|
||||
url: data.url, // Todo: (orgs) check url unique
|
||||
url: data.url, // Todo: orgs check url unique
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZTeamUrlSchema } from '../team-router/schema';
|
||||
import { ZCreateOrganisationRequestSchema } from './create-organisation.types';
|
||||
|
||||
// export const updateOrganisationMeta: TrpcOpenApiMeta = {
|
||||
@ -15,7 +16,8 @@ import { ZCreateOrganisationRequestSchema } from './create-organisation.types';
|
||||
export const ZUpdateOrganisationRequestSchema = z.object({
|
||||
data: ZCreateOrganisationRequestSchema.pick({
|
||||
name: true,
|
||||
url: true,
|
||||
}).extend({
|
||||
url: ZTeamUrlSchema,
|
||||
}),
|
||||
organisationId: z.string(),
|
||||
});
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image';
|
||||
import { createBillingPortal } from '@documenso/lib/server-only/user/create-billing-portal';
|
||||
import { createCheckoutSession } from '@documenso/lib/server-only/user/create-checkout-session';
|
||||
import { deleteUser } from '@documenso/lib/server-only/user/delete-user';
|
||||
import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs';
|
||||
import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id';
|
||||
@ -8,7 +6,6 @@ import { updateProfile } from '@documenso/lib/server-only/user/update-profile';
|
||||
|
||||
import { adminProcedure, authenticatedProcedure, router } from '../trpc';
|
||||
import {
|
||||
ZCreateCheckoutSessionRequestSchema,
|
||||
ZFindUserSecurityAuditLogsSchema,
|
||||
ZRetrieveUserByIdQuerySchema,
|
||||
ZSetProfileImageMutationSchema,
|
||||
@ -31,31 +28,6 @@ export const profileRouter = router({
|
||||
return await getUserById({ id });
|
||||
}),
|
||||
|
||||
createBillingPortal: authenticatedProcedure.mutation(async ({ ctx }) => {
|
||||
return await createBillingPortal({
|
||||
user: {
|
||||
id: ctx.user.id,
|
||||
customerId: ctx.user.customerId,
|
||||
email: ctx.user.email,
|
||||
name: ctx.user.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
createCheckoutSession: authenticatedProcedure
|
||||
.input(ZCreateCheckoutSessionRequestSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return await createCheckoutSession({
|
||||
user: {
|
||||
id: ctx.user.id,
|
||||
customerId: ctx.user.customerId,
|
||||
email: ctx.user.email,
|
||||
name: ctx.user.name,
|
||||
},
|
||||
priceId: input.priceId,
|
||||
});
|
||||
}),
|
||||
|
||||
updateProfile: authenticatedProcedure
|
||||
.input(ZUpdateProfileMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@ -13,10 +13,6 @@ export const ZRetrieveUserByIdQuerySchema = z.object({
|
||||
|
||||
export type TRetrieveUserByIdQuerySchema = z.infer<typeof ZRetrieveUserByIdQuerySchema>;
|
||||
|
||||
export const ZCreateCheckoutSessionRequestSchema = z.object({
|
||||
priceId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZUpdateProfileMutationSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
signature: z.string(),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { adminRouter } from './admin-router/router';
|
||||
import { apiTokenRouter } from './api-token-router/router';
|
||||
import { authRouter } from './auth-router/router';
|
||||
import { billingRouter } from './billing/router';
|
||||
import { documentRouter } from './document-router/router';
|
||||
import { fieldRouter } from './field-router/router';
|
||||
import { organisationRouter } from './organisation-router/router';
|
||||
@ -14,6 +15,7 @@ import { webhookRouter } from './webhook-router/router';
|
||||
|
||||
export const appRouter = router({
|
||||
auth: authRouter,
|
||||
billing: billingRouter,
|
||||
profile: profileRouter,
|
||||
document: documentRouter,
|
||||
field: fieldRouter,
|
||||
|
||||
@ -24,15 +24,6 @@ export const ZCreateTeamRequestSchema = z.object({
|
||||
),
|
||||
});
|
||||
|
||||
export const ZCreateTeamResponseSchema = z.union([
|
||||
z.object({
|
||||
paymentRequired: z.literal(false),
|
||||
}),
|
||||
z.object({
|
||||
paymentRequired: z.literal(true),
|
||||
pendingTeamId: z.number(),
|
||||
}),
|
||||
]);
|
||||
export const ZCreateTeamResponseSchema = z.void();
|
||||
|
||||
export type TCreateTeamRequest = z.infer<typeof ZCreateTeamRequestSchema>;
|
||||
export type TCreateTeamResponse = z.infer<typeof ZCreateTeamResponseSchema>;
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { getTeamPrices } from '@documenso/ee/server-only/stripe/get-team-prices';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createTeamPendingCheckoutSession } from '@documenso/lib/server-only/team/create-team-checkout-session';
|
||||
import { createTeamEmailVerification } from '@documenso/lib/server-only/team/create-team-email-verification';
|
||||
import { deleteTeamEmail } from '@documenso/lib/server-only/team/delete-team-email';
|
||||
import { deleteTeamEmailVerification } from '@documenso/lib/server-only/team/delete-team-email-verification';
|
||||
import { deleteTeamPending } from '@documenso/lib/server-only/team/delete-team-pending';
|
||||
import { findTeamsPending } from '@documenso/lib/server-only/team/find-teams-pending';
|
||||
import { getTeamEmailByEmail } from '@documenso/lib/server-only/team/get-team-email-by-email';
|
||||
import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/resend-team-email-verification';
|
||||
import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email';
|
||||
@ -27,11 +23,8 @@ import { getTeamRoute } from './get-team';
|
||||
import { getTeamMembersRoute } from './get-team-members';
|
||||
import {
|
||||
ZCreateTeamEmailVerificationMutationSchema,
|
||||
ZCreateTeamPendingCheckoutMutationSchema,
|
||||
ZDeleteTeamEmailMutationSchema,
|
||||
ZDeleteTeamEmailVerificationMutationSchema,
|
||||
ZDeleteTeamPendingMutationSchema,
|
||||
ZFindTeamsPendingQuerySchema,
|
||||
ZResendTeamEmailVerificationMutationSchema,
|
||||
ZUpdateTeamEmailMutationSchema,
|
||||
ZUpdateTeamPublicProfileMutationSchema,
|
||||
@ -65,41 +58,61 @@ export const teamRouter = router({
|
||||
},
|
||||
|
||||
// Old routes (to be migrated)
|
||||
|
||||
// Internal endpoint for now.
|
||||
createTeamEmailVerification: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}/email/create',
|
||||
// summary: 'Create team email',
|
||||
// description: 'Add an email to a team and send an email request to verify it',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZCreateTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await createTeamEmailVerification({
|
||||
teamId: input.teamId,
|
||||
userId: ctx.user.id,
|
||||
data: {
|
||||
email: input.email,
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
// Todo: Refactor into routes.
|
||||
email: {
|
||||
get: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
return await getTeamEmailByEmail({ email: ctx.user.email });
|
||||
}),
|
||||
update: authenticatedProcedure
|
||||
.input(ZUpdateTeamEmailMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await updateTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
delete: authenticatedProcedure
|
||||
.input(ZDeleteTeamEmailMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await deleteTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
userEmail: ctx.user.email,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
verification: {
|
||||
send: authenticatedProcedure
|
||||
.input(ZCreateTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await createTeamEmailVerification({
|
||||
teamId: input.teamId,
|
||||
userId: ctx.user.id,
|
||||
data: {
|
||||
email: input.email,
|
||||
name: input.name,
|
||||
},
|
||||
});
|
||||
}),
|
||||
resend: authenticatedProcedure
|
||||
.input(ZResendTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await resendTeamEmailVerification({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
delete: authenticatedProcedure
|
||||
.input(ZDeleteTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await deleteTeamEmailVerification({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
// Todo: Public endpoint.
|
||||
updateTeamPublicProfile: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}/profile',
|
||||
// summary: 'Update a team public profile',
|
||||
// description: '',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZUpdateTeamPublicProfileMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
@ -129,137 +142,4 @@ export const teamRouter = router({
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
// Todo
|
||||
getTeamEmailByEmail: authenticatedProcedure.query(async ({ ctx }) => {
|
||||
return await getTeamEmailByEmail({ email: ctx.user.email });
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
updateTeamEmail: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}/email',
|
||||
// summary: 'Update a team email',
|
||||
// description: '',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZUpdateTeamEmailMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await updateTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
deleteTeamEmail: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}/email/delete',
|
||||
// summary: 'Delete team email',
|
||||
// description: '',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZDeleteTeamEmailMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await deleteTeamEmail({
|
||||
userId: ctx.user.id,
|
||||
userEmail: ctx.user.email,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
resendTeamEmailVerification: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}/email/resend',
|
||||
// summary: 'Resend team email verification',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZResendTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await resendTeamEmailVerification({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
deleteTeamEmailVerification: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/{teamId}/email/verify/delete',
|
||||
// summary: 'Delete team email verification',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZDeleteTeamEmailVerificationMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await deleteTeamEmailVerification({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
createTeamPendingCheckout: authenticatedProcedure
|
||||
.input(ZCreateTeamPendingCheckoutMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await createTeamPendingCheckoutSession({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
getTeamPrices: authenticatedProcedure.query(async () => {
|
||||
return await getTeamPrices();
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
findTeamsPending: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'GET',
|
||||
// path: '/team/pending',
|
||||
// summary: 'Find pending teams',
|
||||
// description: 'Find teams that are pending payment',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZFindTeamsPendingQuerySchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return await findTeamsPending({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
|
||||
// Internal endpoint for now.
|
||||
deleteTeamPending: authenticatedProcedure
|
||||
// .meta({
|
||||
// openapi: {
|
||||
// method: 'POST',
|
||||
// path: '/team/pending/{pendingTeamId}/delete',
|
||||
// summary: 'Delete pending team',
|
||||
// description: '',
|
||||
// tags: ['Teams'],
|
||||
// },
|
||||
// })
|
||||
.input(ZDeleteTeamPendingMutationSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await deleteTeamPending({
|
||||
userId: ctx.user.id,
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@ -2,7 +2,6 @@ import { TeamMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams';
|
||||
import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const MAX_PROFILE_BIO_LENGTH = 256;
|
||||
|
||||
@ -49,11 +48,6 @@ export const ZCreateTeamEmailVerificationMutationSchema = z.object({
|
||||
email: z.string().trim().email().toLowerCase().min(1, 'Please enter a valid email.'),
|
||||
});
|
||||
|
||||
export const ZCreateTeamPendingCheckoutMutationSchema = z.object({
|
||||
interval: z.union([z.literal('monthly'), z.literal('yearly')]),
|
||||
pendingTeamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteTeamEmailMutationSchema = z.object({
|
||||
teamId: z.number(),
|
||||
});
|
||||
@ -62,16 +56,6 @@ export const ZDeleteTeamEmailVerificationMutationSchema = z.object({
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteTeamMutationSchema = z.object({
|
||||
teamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZDeleteTeamPendingMutationSchema = z.object({
|
||||
pendingTeamId: z.number(),
|
||||
});
|
||||
|
||||
export const ZFindTeamsPendingQuerySchema = ZFindSearchParamsSchema;
|
||||
|
||||
export const ZGetTeamMembersQuerySchema = z.object({
|
||||
teamId: z.number(),
|
||||
});
|
||||
@ -119,13 +103,7 @@ export type TCreateTeamEmailVerificationMutationSchema = z.infer<
|
||||
typeof ZCreateTeamEmailVerificationMutationSchema
|
||||
>;
|
||||
|
||||
export type TCreateTeamPendingCheckoutMutationSchema = z.infer<
|
||||
typeof ZCreateTeamPendingCheckoutMutationSchema
|
||||
>;
|
||||
export type TDeleteTeamEmailMutationSchema = z.infer<typeof ZDeleteTeamEmailMutationSchema>;
|
||||
export type TDeleteTeamMutationSchema = z.infer<typeof ZDeleteTeamMutationSchema>;
|
||||
export type TDeleteTeamPendingMutationSchema = z.infer<typeof ZDeleteTeamPendingMutationSchema>;
|
||||
export type TFindTeamsPendingQuerySchema = z.infer<typeof ZFindTeamsPendingQuerySchema>;
|
||||
export type TGetTeamMembersQuerySchema = z.infer<typeof ZGetTeamMembersQuerySchema>;
|
||||
export type TUpdateTeamEmailMutationSchema = z.infer<typeof ZUpdateTeamEmailMutationSchema>;
|
||||
export type TResendTeamEmailVerificationMutationSchema = z.infer<
|
||||
|
||||
Reference in New Issue
Block a user