mirror of
https://github.com/documenso/documenso.git
synced 2026-07-21 15:33:40 +10:00
chore: merge main, resolve biome formatting conflicts
Merge origin/main into feat/external-2fa-codes. Resolve formatting conflicts caused by biome rollout; preserve both feature streams: PR's external 2FA token + signing-session 2FA proof additions plus main's RateLimit/RecipientExpired/signingReminders/date-auto-insert. In complete-document-with-token.ts, drop the duplicate early field-fetching block introduced when main moved that logic later with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH check using derivedRecipientActionAuth.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { OrganisationType } from '@prisma/client';
|
||||
|
||||
import { createOrganisation } from '@documenso/lib/server-only/organisation/create-organisation';
|
||||
import { internalClaims } from '@documenso/lib/types/subscription';
|
||||
import { OrganisationType } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
|
||||
@@ -38,19 +38,19 @@ export const createStripeCustomerRoute = adminProcedure
|
||||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const stripeCustomer = await createCustomer({
|
||||
name: organisation.name,
|
||||
email: organisation.owner.email,
|
||||
});
|
||||
// Create Stripe customer outside a transaction to avoid holding a
|
||||
// connection open during the external API call.
|
||||
const stripeCustomer = await createCustomer({
|
||||
name: organisation.name,
|
||||
email: organisation.owner.email,
|
||||
});
|
||||
|
||||
await tx.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
customerId: stripeCustomer.id,
|
||||
},
|
||||
});
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
data: {
|
||||
customerId: stripeCustomer.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZClaimFlagsSchema } from '@documenso/lib/types/subscription';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateSubscriptionClaimRequestSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
|
||||
@@ -2,10 +2,7 @@ import { adminSuperDeleteDocument } from '@documenso/lib/server-only/admin/admin
|
||||
import { sendDeleteEmail } from '@documenso/lib/server-only/document/send-delete-email';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteDocumentRequestSchema,
|
||||
ZDeleteDocumentResponseSchema,
|
||||
} from './delete-document.types';
|
||||
import { ZDeleteDocumentRequestSchema, ZDeleteDocumentResponseSchema } from './delete-document.types';
|
||||
|
||||
export const deleteDocumentRoute = adminProcedure
|
||||
.input(ZDeleteDocumentRequestSchema)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
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 { prisma } from '@documenso/prisma';
|
||||
import { OrganisationMemberInviteStatus } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZDeleteAdminOrganisationMemberRequestSchema,
|
||||
ZDeleteAdminOrganisationMemberResponseSchema,
|
||||
} from './delete-organisation-member.types';
|
||||
|
||||
export const deleteAdminOrganisationMemberRoute = adminProcedure
|
||||
.input(ZDeleteAdminOrganisationMemberRequestSchema)
|
||||
.output(ZDeleteAdminOrganisationMemberResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { organisationId, organisationMemberId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
organisationMemberId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findUnique({
|
||||
where: {
|
||||
id: organisationId,
|
||||
},
|
||||
include: {
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
teams: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
invites: {
|
||||
where: {
|
||||
status: OrganisationMemberInviteStatus.PENDING,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Organisation not found',
|
||||
});
|
||||
}
|
||||
|
||||
const memberToDelete = organisation.members.find((member) => member.id === organisationMemberId);
|
||||
|
||||
if (!memberToDelete) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Member not found in this organisation',
|
||||
});
|
||||
}
|
||||
|
||||
if (memberToDelete.userId === organisation.ownerUserId) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Cannot remove the organisation owner. Transfer ownership first.',
|
||||
});
|
||||
}
|
||||
|
||||
const newMemberCount = organisation.members.length + organisation.invites.length - 1;
|
||||
|
||||
// Removing a member is a reducing operation, so we don't gate it on the
|
||||
// subscription being present. Sync Stripe only when one exists.
|
||||
if (organisation.subscription) {
|
||||
await syncMemberCountWithStripeSeatPlan(
|
||||
organisation.subscription,
|
||||
organisation.organisationClaim,
|
||||
newMemberCount,
|
||||
);
|
||||
}
|
||||
|
||||
const teamIds = organisation.teams.map((team) => team.id);
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Removing an OrganisationMember cascades the user out of every team in
|
||||
// the org via OrganisationGroupMember, but their authored Envelope rows
|
||||
// still reference them. Reassign those to the org owner so they remain
|
||||
// reachable after the member loses access (mirrors delete-user.ts).
|
||||
if (teamIds.length > 0) {
|
||||
await tx.envelope.updateMany({
|
||||
where: {
|
||||
userId: memberToDelete.userId,
|
||||
teamId: {
|
||||
in: teamIds,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
userId: organisation.ownerUserId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await tx.organisationMember.delete({
|
||||
where: {
|
||||
id: organisationMemberId,
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.organisation-member-left.email',
|
||||
payload: {
|
||||
organisationId,
|
||||
memberUserId: memberToDelete.userId,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteAdminOrganisationMemberRequestSchema = z.object({
|
||||
organisationId: z.string().min(1),
|
||||
organisationMemberId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZDeleteAdminOrganisationMemberResponseSchema = z.void();
|
||||
|
||||
export type TDeleteAdminOrganisationMemberRequest = z.infer<typeof ZDeleteAdminOrganisationMemberRequestSchema>;
|
||||
export type TDeleteAdminOrganisationMemberResponse = z.infer<typeof ZDeleteAdminOrganisationMemberResponseSchema>;
|
||||
@@ -0,0 +1,101 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationGroupType } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZDeleteAdminTeamMemberRequestSchema, ZDeleteAdminTeamMemberResponseSchema } from './delete-team-member.types';
|
||||
|
||||
export const deleteAdminTeamMemberRoute = adminProcedure
|
||||
.input(ZDeleteAdminTeamMemberRequestSchema)
|
||||
.output(ZDeleteAdminTeamMemberResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { teamId, memberId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
memberId,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
include: {
|
||||
organisation: {
|
||||
select: {
|
||||
ownerUserId: true,
|
||||
},
|
||||
},
|
||||
teamGroups: {
|
||||
where: {
|
||||
organisationGroup: {
|
||||
type: OrganisationGroupType.INTERNAL_TEAM,
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
id: memberId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team not found',
|
||||
});
|
||||
}
|
||||
|
||||
const teamGroupToRemoveMemberFrom = team.teamGroups[0];
|
||||
|
||||
if (!teamGroupToRemoveMemberFrom) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Member is not directly assigned to this team. Inherited members cannot be removed here.',
|
||||
});
|
||||
}
|
||||
|
||||
const member = await prisma.organisationMember.findUnique({
|
||||
where: {
|
||||
id: memberId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!member) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Member not found',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Removing a user from a single team drops their INTERNAL_TEAM
|
||||
// OrganisationGroupMember link, but Envelope rows they authored in this
|
||||
// team still point at their userId. Reassign to the org owner so those
|
||||
// envelopes remain reachable after the member loses team access.
|
||||
await tx.envelope.updateMany({
|
||||
where: {
|
||||
userId: member.userId,
|
||||
teamId,
|
||||
},
|
||||
data: {
|
||||
userId: team.organisation.ownerUserId,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.organisationGroupMember.delete({
|
||||
where: {
|
||||
organisationMemberId_groupId: {
|
||||
organisationMemberId: memberId,
|
||||
groupId: teamGroupToRemoveMemberFrom.organisationGroupId,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDeleteAdminTeamMemberRequestSchema = z.object({
|
||||
teamId: z.number().min(1),
|
||||
memberId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const ZDeleteAdminTeamMemberResponseSchema = z.void();
|
||||
|
||||
export type TDeleteAdminTeamMemberRequest = z.infer<typeof ZDeleteAdminTeamMemberRequestSchema>;
|
||||
export type TDeleteAdminTeamMemberResponse = z.infer<typeof ZDeleteAdminTeamMemberResponseSchema>;
|
||||
@@ -0,0 +1,81 @@
|
||||
import { PDF_SIZE_A4_72PPI } from '@documenso/lib/constants/pdf';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { generateAuditLogPdf } from '@documenso/lib/server-only/pdf/generate-audit-log-pdf';
|
||||
import { unsafeBuildEnvelopeIdQuery } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZAdminDownloadDocumentAuditLogsRequestSchema,
|
||||
ZAdminDownloadDocumentAuditLogsResponseSchema,
|
||||
} from './download-document-audit-logs.types';
|
||||
|
||||
export const downloadDocumentAuditLogsRoute = adminProcedure
|
||||
.input(ZAdminDownloadDocumentAuditLogsRequestSchema)
|
||||
.output(ZAdminDownloadDocumentAuditLogsResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { envelopeId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: unsafeBuildEnvelopeIdQuery(
|
||||
{
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
EnvelopeType.DOCUMENT,
|
||||
),
|
||||
include: {
|
||||
documentMeta: true,
|
||||
envelopeItems: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
recipients: {
|
||||
orderBy: {
|
||||
id: 'asc',
|
||||
},
|
||||
},
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
const auditLogPdf = await generateAuditLogPdf({
|
||||
envelope,
|
||||
recipients: envelope.recipients,
|
||||
fields: envelope.fields,
|
||||
language: envelope.documentMeta.language,
|
||||
envelopeOwner: {
|
||||
email: envelope.user.email,
|
||||
name: envelope.user.name || '',
|
||||
},
|
||||
envelopeItems: envelope.envelopeItems.map((item) => item.title),
|
||||
pageWidth: PDF_SIZE_A4_72PPI.width,
|
||||
pageHeight: PDF_SIZE_A4_72PPI.height,
|
||||
});
|
||||
|
||||
const result = await auditLogPdf.save();
|
||||
|
||||
const base64 = Buffer.from(result).toString('base64');
|
||||
|
||||
return {
|
||||
data: base64,
|
||||
envelopeTitle: envelope.title,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAdminDownloadDocumentAuditLogsRequestSchema = z.object({
|
||||
envelopeId: z.string(),
|
||||
});
|
||||
|
||||
export const ZAdminDownloadDocumentAuditLogsResponseSchema = z.object({
|
||||
data: z.string(),
|
||||
envelopeTitle: z.string(),
|
||||
});
|
||||
|
||||
export type TAdminDownloadDocumentAuditLogsRequest = z.infer<typeof ZAdminDownloadDocumentAuditLogsRequestSchema>;
|
||||
export type TAdminDownloadDocumentAuditLogsResponse = z.infer<typeof ZAdminDownloadDocumentAuditLogsResponseSchema>;
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -120,14 +119,16 @@ export const findAdminOrganisations = async ({
|
||||
};
|
||||
}
|
||||
|
||||
const orderBy: Prisma.OrganisationOrderByWithRelationInput[] = query
|
||||
? [{ subscription: { status: 'asc' } }, { name: 'asc' }]
|
||||
: [{ createdAt: 'desc' }];
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.organisation.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
orderBy,
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { 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';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindAdminOrganisationsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
ownerUserId: z.number().optional(),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { parseDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { unsafeBuildEnvelopeIdQuery } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -16,13 +15,7 @@ export const findDocumentAuditLogsRoute = adminProcedure
|
||||
.input(ZFindDocumentAuditLogsRequestSchema)
|
||||
.output(ZFindDocumentAuditLogsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const {
|
||||
envelopeId,
|
||||
page = 1,
|
||||
perPage = 50,
|
||||
orderByColumn = 'createdAt',
|
||||
orderByDirection = 'desc',
|
||||
} = input;
|
||||
const { envelopeId, page = 1, perPage = 50, orderByColumn = 'createdAt', orderByDirection = 'desc' } = input;
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: unsafeBuildEnvelopeIdQuery(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentAuditLogSchema } from '@documenso/lib/types/document-audit-logs';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindDocumentAuditLogsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
envelopeId: z.string(),
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import {
|
||||
mapSecondaryIdToDocumentId,
|
||||
unsafeBuildEnvelopeIdQuery,
|
||||
} from '@documenso/lib/utils/envelope';
|
||||
import { mapSecondaryIdToDocumentId, unsafeBuildEnvelopeIdQuery } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentJobsRequestSchema,
|
||||
ZFindDocumentJobsResponseSchema,
|
||||
} from './find-document-jobs.types';
|
||||
import { ZFindDocumentJobsRequestSchema, ZFindDocumentJobsResponseSchema } from './find-document-jobs.types';
|
||||
|
||||
export const findDocumentJobsRoute = adminProcedure
|
||||
.input(ZFindDocumentJobsRequestSchema)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import BackgroundJobSchema from '@documenso/prisma/generated/zod/modelSchema/BackgroundJobSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindDocumentJobsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
envelopeId: z.string(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentManySchema } from '@documenso/lib/types/document';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindDocumentsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
perPage: z.number().optional().default(20),
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZFindEmailDomainsRequestSchema, ZFindEmailDomainsResponseSchema } from './find-email-domains.types';
|
||||
|
||||
export const findEmailDomainsRoute = adminProcedure
|
||||
.input(ZFindEmailDomainsRequestSchema)
|
||||
.output(ZFindEmailDomainsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { query, page, perPage, status } = input;
|
||||
|
||||
return await findEmailDomains({ query, page, perPage, status });
|
||||
});
|
||||
|
||||
type FindEmailDomainsOptions = {
|
||||
query?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
status?: 'PENDING' | 'ACTIVE';
|
||||
};
|
||||
|
||||
const findEmailDomains = async ({ query, page = 1, perPage = 20, status }: FindEmailDomainsOptions) => {
|
||||
const whereClause: Prisma.EmailDomainWhereInput = {};
|
||||
|
||||
if (query) {
|
||||
whereClause.OR = [
|
||||
{
|
||||
domain: {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
organisation: {
|
||||
name: {
|
||||
contains: query,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.emailDomain.findMany({
|
||||
where: whereClause,
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
domain: true,
|
||||
status: true,
|
||||
selector: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
lastVerifiedAt: true,
|
||||
organisation: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
emails: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.emailDomain.count({
|
||||
where: whereClause,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
data,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import EmailDomainStatusSchema from '@documenso/prisma/generated/zod/inputTypeSchemas/EmailDomainStatusSchema';
|
||||
import EmailDomainSchema from '@documenso/prisma/generated/zod/modelSchema/EmailDomainSchema';
|
||||
import OrganisationSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindEmailDomainsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
status: EmailDomainStatusSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZFindEmailDomainsResponseSchema = ZFindResultResponse.extend({
|
||||
data: EmailDomainSchema.pick({
|
||||
id: true,
|
||||
domain: true,
|
||||
status: true,
|
||||
selector: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
lastVerifiedAt: true,
|
||||
})
|
||||
.extend({
|
||||
organisation: OrganisationSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
}),
|
||||
_count: z.object({
|
||||
emails: z.number(),
|
||||
}),
|
||||
})
|
||||
.array(),
|
||||
});
|
||||
|
||||
export type TFindEmailDomainsRequest = z.infer<typeof ZFindEmailDomainsRequestSchema>;
|
||||
export type TFindEmailDomainsResponse = z.infer<typeof ZFindEmailDomainsResponseSchema>;
|
||||
@@ -1,9 +1,8 @@
|
||||
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 type { Prisma } from '@prisma/client';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -26,11 +25,7 @@ type FindSubscriptionClaimsOptions = {
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const findSubscriptionClaims = async ({
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 50,
|
||||
}: FindSubscriptionClaimsOptions) => {
|
||||
export const findSubscriptionClaims = async ({ query, page = 1, perPage = 50 }: FindSubscriptionClaimsOptions) => {
|
||||
let whereClause: Prisma.SubscriptionClaimWhereInput = {};
|
||||
|
||||
if (query) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import SubscriptionClaimSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionClaimSchema';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export const ZFindSubscriptionClaimsRequestSchema = ZFindSearchParamsSchema.extend({});
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { adminFindUnsealedDocuments } from '@documenso/lib/server-only/admin/admin-find-unsealed-documents';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindUnsealedDocumentsRequestSchema,
|
||||
ZFindUnsealedDocumentsResponseSchema,
|
||||
} from './find-unsealed-documents.types';
|
||||
|
||||
export const findUnsealedDocumentsRoute = adminProcedure
|
||||
.input(ZFindUnsealedDocumentsRequestSchema)
|
||||
.output(ZFindUnsealedDocumentsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { page, perPage } = input;
|
||||
|
||||
return await adminFindUnsealedDocuments({ page, perPage });
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindUnsealedDocumentsRequestSchema = ZFindSearchParamsSchema.pick({
|
||||
page: true,
|
||||
perPage: true,
|
||||
}).extend({
|
||||
perPage: z.number().optional().default(20),
|
||||
});
|
||||
|
||||
export const ZAdminUnsealedDocumentSchema = z.object({
|
||||
id: z.string(),
|
||||
secondaryId: z.string(),
|
||||
title: z.string(),
|
||||
status: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
userId: z.number(),
|
||||
teamId: z.number(),
|
||||
ownerName: z.string().nullable(),
|
||||
ownerEmail: z.string(),
|
||||
lastSignedAt: z.date().nullable(),
|
||||
});
|
||||
|
||||
export const ZFindUnsealedDocumentsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZAdminUnsealedDocumentSchema.array(),
|
||||
});
|
||||
|
||||
export type TFindUnsealedDocumentsRequest = z.infer<typeof ZFindUnsealedDocumentsRequestSchema>;
|
||||
export type TFindUnsealedDocumentsResponse = z.infer<typeof ZFindUnsealedDocumentsResponseSchema>;
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { getHighestTeamRoleInGroup } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZFindUserTeamsRequestSchema, ZFindUserTeamsResponseSchema } from './find-user-teams.types';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { TeamMemberRoleSchema } from '@documenso/prisma/generated/zod/inputTypeSchemas/TeamMemberRoleSchema';
|
||||
import OrganisationSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationSchema';
|
||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindUserTeamsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
userId: z.number(),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
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';
|
||||
@@ -9,6 +7,7 @@ import OrganisationMemberSchema from '@documenso/prisma/generated/zod/modelSchem
|
||||
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';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetAdminOrganisationRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getHighestOrganisationRoleInGroup } from '@documenso/lib/utils/organisations';
|
||||
import { getHighestTeamRoleInGroup } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationMemberInviteStatus } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZGetAdminTeamRequestSchema, ZGetAdminTeamResponseSchema } from './get-admin-team.types';
|
||||
|
||||
export const getAdminTeamRoute = adminProcedure
|
||||
.input(ZGetAdminTeamRequestSchema)
|
||||
.output(ZGetAdminTeamResponseSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const { teamId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await prisma.team.findUnique({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
include: {
|
||||
organisation: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
ownerUserId: true,
|
||||
},
|
||||
},
|
||||
teamEmail: true,
|
||||
teamGlobalSettings: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Team not found',
|
||||
});
|
||||
}
|
||||
|
||||
const [teamMembers, pendingInvites] = await Promise.all([
|
||||
prisma.organisationMember.findMany({
|
||||
where: {
|
||||
organisationId: team.organisationId,
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
group: {
|
||||
teamGroups: {
|
||||
some: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
createdAt: true,
|
||||
organisationGroupMembers: {
|
||||
include: {
|
||||
group: {
|
||||
include: {
|
||||
teamGroups: {
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Invites are organisation-scoped in the schema (no team relation), so this is intentionally
|
||||
// all pending invites for the team's parent organisation.
|
||||
prisma.organisationMemberInvite.findMany({
|
||||
where: {
|
||||
organisationId: team.organisationId,
|
||||
status: OrganisationMemberInviteStatus.PENDING,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
createdAt: true,
|
||||
organisationRole: true,
|
||||
status: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const mappedTeamMembers = teamMembers.map((teamMember) => {
|
||||
const groups = teamMember.organisationGroupMembers.map(({ group }) => group);
|
||||
|
||||
return {
|
||||
id: teamMember.id,
|
||||
userId: teamMember.userId,
|
||||
createdAt: teamMember.createdAt,
|
||||
user: teamMember.user,
|
||||
teamRole: getHighestTeamRoleInGroup(groups.flatMap((group) => group.teamGroups)),
|
||||
organisationRole: getHighestOrganisationRoleInGroup(groups),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...team,
|
||||
memberCount: mappedTeamMembers.length,
|
||||
teamMembers: mappedTeamMembers,
|
||||
pendingInvites,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { OrganisationMemberRoleSchema } from '@documenso/prisma/generated/zod/inputTypeSchemas/OrganisationMemberRoleSchema';
|
||||
import { TeamMemberRoleSchema } from '@documenso/prisma/generated/zod/inputTypeSchemas/TeamMemberRoleSchema';
|
||||
import OrganisationMemberInviteSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationMemberInviteSchema';
|
||||
import OrganisationMemberSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationMemberSchema';
|
||||
import OrganisationSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationSchema';
|
||||
import TeamEmailSchema from '@documenso/prisma/generated/zod/modelSchema/TeamEmailSchema';
|
||||
import TeamGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/TeamGlobalSettingsSchema';
|
||||
import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetAdminTeamRequestSchema = z.object({
|
||||
teamId: z.number().min(1),
|
||||
});
|
||||
|
||||
export const ZGetAdminTeamResponseSchema = TeamSchema.extend({
|
||||
organisation: OrganisationSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
ownerUserId: true,
|
||||
}),
|
||||
teamEmail: TeamEmailSchema.nullable(),
|
||||
teamGlobalSettings: TeamGlobalSettingsSchema.nullable(),
|
||||
memberCount: z.number(),
|
||||
teamMembers: OrganisationMemberSchema.pick({
|
||||
id: true,
|
||||
userId: true,
|
||||
createdAt: true,
|
||||
})
|
||||
.extend({
|
||||
user: UserSchema.pick({
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
}),
|
||||
teamRole: TeamMemberRoleSchema,
|
||||
organisationRole: OrganisationMemberRoleSchema,
|
||||
})
|
||||
.array(),
|
||||
pendingInvites: OrganisationMemberInviteSchema.pick({
|
||||
id: true,
|
||||
email: true,
|
||||
createdAt: true,
|
||||
organisationRole: true,
|
||||
status: true,
|
||||
}).array(),
|
||||
});
|
||||
|
||||
export type TGetAdminTeamRequest = z.infer<typeof ZGetAdminTeamRequestSchema>;
|
||||
export type TGetAdminTeamResponse = z.infer<typeof ZGetAdminTeamResponseSchema>;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import { ZGetEmailDomainRequestSchema, ZGetEmailDomainResponseSchema } from './get-email-domain.types';
|
||||
|
||||
export const getEmailDomainRoute = adminProcedure
|
||||
.input(ZGetEmailDomainRequestSchema)
|
||||
.output(ZGetEmailDomainResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const { emailDomainId } = input;
|
||||
|
||||
const emailDomain = await prisma.emailDomain.findUnique({
|
||||
where: {
|
||||
id: emailDomainId,
|
||||
},
|
||||
omit: {
|
||||
privateKey: true,
|
||||
},
|
||||
include: {
|
||||
organisation: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
},
|
||||
},
|
||||
emails: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!emailDomain) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Email domain not found',
|
||||
});
|
||||
}
|
||||
|
||||
return emailDomain;
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ZOrganisationEmailLiteSchema } from '@documenso/lib/types/organisation-email';
|
||||
import EmailDomainSchema from '@documenso/prisma/generated/zod/modelSchema/EmailDomainSchema';
|
||||
import OrganisationSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetEmailDomainRequestSchema = z.object({
|
||||
emailDomainId: z.string(),
|
||||
});
|
||||
|
||||
export const ZGetEmailDomainResponseSchema = EmailDomainSchema.pick({
|
||||
id: true,
|
||||
domain: true,
|
||||
status: true,
|
||||
selector: true,
|
||||
publicKey: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
lastVerifiedAt: true,
|
||||
}).extend({
|
||||
organisation: OrganisationSchema.pick({
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
}),
|
||||
emails: ZOrganisationEmailLiteSchema.array(),
|
||||
});
|
||||
|
||||
export type TGetEmailDomainRequest = z.infer<typeof ZGetEmailDomainRequestSchema>;
|
||||
export type TGetEmailDomainResponse = z.infer<typeof ZGetEmailDomainResponseSchema>;
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZGetUserRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { generateDatabaseId } from '@documenso/lib/universal/id';
|
||||
import { getHighestOrganisationRoleInGroup } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -78,13 +77,9 @@ export const promoteMemberToOwnerRoute = adminProcedure
|
||||
);
|
||||
|
||||
// Find the current and target organisation groups
|
||||
const currentMemberGroup = organisation.groups.find(
|
||||
(group) => group.organisationRole === currentOrganisationRole,
|
||||
);
|
||||
const currentMemberGroup = organisation.groups.find((group) => group.organisationRole === currentOrganisationRole);
|
||||
|
||||
const adminGroup = organisation.groups.find(
|
||||
(group) => group.organisationRole === OrganisationMemberRole.ADMIN,
|
||||
);
|
||||
const adminGroup = organisation.groups.find((group) => group.organisationRole === OrganisationMemberRole.ADMIN);
|
||||
|
||||
if (!currentMemberGroup) {
|
||||
throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { reregisterEmailDomain } from '@documenso/ee/server-only/lib/reregister-email-domain';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZReregisterEmailDomainRequestSchema,
|
||||
ZReregisterEmailDomainResponseSchema,
|
||||
} from './reregister-email-domain.types';
|
||||
|
||||
export const reregisterEmailDomainRoute = adminProcedure
|
||||
.input(ZReregisterEmailDomainRequestSchema)
|
||||
.output(ZReregisterEmailDomainResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { emailDomainId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
emailDomainId,
|
||||
},
|
||||
});
|
||||
|
||||
await reregisterEmailDomain({ emailDomainId });
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZReregisterEmailDomainRequestSchema = z.object({
|
||||
emailDomainId: z.string(),
|
||||
});
|
||||
|
||||
export const ZReregisterEmailDomainResponseSchema = z.void();
|
||||
|
||||
export type TReregisterEmailDomainRequest = z.infer<typeof ZReregisterEmailDomainRequestSchema>;
|
||||
export type TReregisterEmailDomainResponse = z.infer<typeof ZReregisterEmailDomainResponseSchema>;
|
||||
@@ -1,15 +1,11 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { jobs } from '@documenso/lib/jobs/client';
|
||||
import { unsafeGetEntireEnvelope } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { isDocumentCompleted } from '@documenso/lib/utils/document';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZResealDocumentRequestSchema,
|
||||
ZResealDocumentResponseSchema,
|
||||
} from './reseal-document.types';
|
||||
import { ZResealDocumentRequestSchema, ZResealDocumentResponseSchema } from './reseal-document.types';
|
||||
|
||||
export const resealDocumentRoute = adminProcedure
|
||||
.input(ZResealDocumentRequestSchema)
|
||||
|
||||
@@ -2,10 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZResetTwoFactorRequestSchema,
|
||||
ZResetTwoFactorResponseSchema,
|
||||
} from './reset-two-factor-authentication.types';
|
||||
import { ZResetTwoFactorRequestSchema, ZResetTwoFactorResponseSchema } from './reset-two-factor-authentication.types';
|
||||
|
||||
export const resetTwoFactorRoute = adminProcedure
|
||||
.input(ZResetTwoFactorRequestSchema)
|
||||
|
||||
@@ -3,22 +3,31 @@ import { createAdminOrganisationRoute } from './create-admin-organisation';
|
||||
import { createStripeCustomerRoute } from './create-stripe-customer';
|
||||
import { createSubscriptionClaimRoute } from './create-subscription-claim';
|
||||
import { deleteDocumentRoute } from './delete-document';
|
||||
import { deleteAdminOrganisationMemberRoute } from './delete-organisation-member';
|
||||
import { deleteSubscriptionClaimRoute } from './delete-subscription-claim';
|
||||
import { deleteAdminTeamMemberRoute } from './delete-team-member';
|
||||
import { deleteUserRoute } from './delete-user';
|
||||
import { disableUserRoute } from './disable-user';
|
||||
import { downloadDocumentAuditLogsRoute } from './download-document-audit-logs';
|
||||
import { enableUserRoute } from './enable-user';
|
||||
import { findAdminOrganisationsRoute } from './find-admin-organisations';
|
||||
import { findDocumentAuditLogsRoute } from './find-document-audit-logs';
|
||||
import { findDocumentJobsRoute } from './find-document-jobs';
|
||||
import { findDocumentsRoute } from './find-documents';
|
||||
import { findEmailDomainsRoute } from './find-email-domains';
|
||||
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
|
||||
import { findUnsealedDocumentsRoute } from './find-unsealed-documents';
|
||||
import { findUserTeamsRoute } from './find-user-teams';
|
||||
import { getAdminOrganisationRoute } from './get-admin-organisation';
|
||||
import { getAdminTeamRoute } from './get-admin-team';
|
||||
import { getEmailDomainRoute } from './get-email-domain';
|
||||
import { getUserRoute } from './get-user';
|
||||
import { promoteMemberToOwnerRoute } from './promote-member-to-owner';
|
||||
import { reregisterEmailDomainRoute } from './reregister-email-domain';
|
||||
import { resealDocumentRoute } from './reseal-document';
|
||||
import { resetTwoFactorRoute } from './reset-two-factor-authentication';
|
||||
import { resyncLicenseRoute } from './resync-license';
|
||||
import { swapOrganisationSubscriptionRoute } from './swap-organisation-subscription';
|
||||
import { updateAdminOrganisationRoute } from './update-admin-organisation';
|
||||
import { updateOrganisationMemberRoleRoute } from './update-organisation-member-role';
|
||||
import { updateRecipientRoute } from './update-recipient';
|
||||
@@ -32,10 +41,12 @@ export const adminRouter = router({
|
||||
get: getAdminOrganisationRoute,
|
||||
create: createAdminOrganisationRoute,
|
||||
update: updateAdminOrganisationRoute,
|
||||
swapSubscription: swapOrganisationSubscriptionRoute,
|
||||
},
|
||||
organisationMember: {
|
||||
promoteToOwner: promoteMemberToOwnerRoute,
|
||||
updateRole: updateOrganisationMemberRoleRoute,
|
||||
delete: deleteAdminOrganisationMemberRoute,
|
||||
},
|
||||
claims: {
|
||||
find: findSubscriptionClaimsRoute,
|
||||
@@ -60,13 +71,26 @@ export const adminRouter = router({
|
||||
},
|
||||
document: {
|
||||
find: findDocumentsRoute,
|
||||
findUnsealed: findUnsealedDocumentsRoute,
|
||||
delete: deleteDocumentRoute,
|
||||
reseal: resealDocumentRoute,
|
||||
findJobs: findDocumentJobsRoute,
|
||||
findAuditLogs: findDocumentAuditLogsRoute,
|
||||
downloadAuditLogs: downloadDocumentAuditLogsRoute,
|
||||
},
|
||||
recipient: {
|
||||
update: updateRecipientRoute,
|
||||
},
|
||||
emailDomain: {
|
||||
find: findEmailDomainsRoute,
|
||||
get: getEmailDomainRoute,
|
||||
reregister: reregisterEmailDomainRoute,
|
||||
},
|
||||
team: {
|
||||
get: getAdminTeamRoute,
|
||||
},
|
||||
teamMember: {
|
||||
delete: deleteAdminTeamMemberRoute,
|
||||
},
|
||||
updateSiteSetting: updateSiteSettingRoute,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createOrganisationClaimUpsertData } from '@documenso/lib/server-only/organisation/create-organisation';
|
||||
import { INTERNAL_CLAIM_ID, internalClaims } from '@documenso/lib/types/subscription';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { SubscriptionStatus } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZSwapOrganisationSubscriptionRequestSchema,
|
||||
ZSwapOrganisationSubscriptionResponseSchema,
|
||||
} from './swap-organisation-subscription.types';
|
||||
|
||||
export const swapOrganisationSubscriptionRoute = adminProcedure
|
||||
.input(ZSwapOrganisationSubscriptionRequestSchema)
|
||||
.output(ZSwapOrganisationSubscriptionResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { sourceOrganisationId, targetOrganisationId } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
sourceOrganisationId,
|
||||
targetOrganisationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (sourceOrganisationId === targetOrganisationId) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Source and target organisations must be different',
|
||||
});
|
||||
}
|
||||
|
||||
const sourceOrg = await prisma.organisation.findUnique({
|
||||
where: { id: sourceOrganisationId },
|
||||
include: {
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!sourceOrg) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Source organisation not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!sourceOrg.subscription ||
|
||||
(sourceOrg.subscription.status !== SubscriptionStatus.ACTIVE &&
|
||||
sourceOrg.subscription.status !== SubscriptionStatus.PAST_DUE)
|
||||
) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Source organisation does not have an active subscription',
|
||||
});
|
||||
}
|
||||
|
||||
const targetOrg = await prisma.organisation.findUnique({
|
||||
where: { id: targetOrganisationId },
|
||||
include: {
|
||||
subscription: true,
|
||||
organisationClaim: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!targetOrg) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Target organisation not found',
|
||||
});
|
||||
}
|
||||
|
||||
if (sourceOrg.ownerUserId !== targetOrg.ownerUserId) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Both organisations must be owned by the same user',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
targetOrg.subscription &&
|
||||
(targetOrg.subscription.status === SubscriptionStatus.ACTIVE ||
|
||||
targetOrg.subscription.status === SubscriptionStatus.PAST_DUE)
|
||||
) {
|
||||
throw new AppError(AppErrorCode.INVALID_REQUEST, {
|
||||
message: 'Target organisation already has an active subscription',
|
||||
});
|
||||
}
|
||||
|
||||
const customerId = sourceOrg.customerId ?? sourceOrg.subscription.customerId;
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Delete stale INACTIVE subscription on target if present.
|
||||
if (targetOrg.subscription) {
|
||||
await tx.subscription.delete({
|
||||
where: { id: targetOrg.subscription.id },
|
||||
});
|
||||
}
|
||||
|
||||
// Clear customerId on source org to avoid unique constraint violation.
|
||||
await tx.organisation.update({
|
||||
where: { id: sourceOrganisationId },
|
||||
data: { customerId: null },
|
||||
});
|
||||
|
||||
// Set customerId on target org.
|
||||
await tx.organisation.update({
|
||||
where: { id: targetOrganisationId },
|
||||
data: { customerId },
|
||||
});
|
||||
|
||||
// Move the subscription record to the target org.
|
||||
await tx.subscription.update({
|
||||
where: { id: sourceOrg.subscription!.id },
|
||||
data: { organisationId: targetOrganisationId },
|
||||
});
|
||||
|
||||
// Copy source org's claim entitlements to target org's claim.
|
||||
if (sourceOrg.organisationClaim && targetOrg.organisationClaim) {
|
||||
await tx.organisationClaim.update({
|
||||
where: { id: targetOrg.organisationClaim.id },
|
||||
data: {
|
||||
originalSubscriptionClaimId: sourceOrg.organisationClaim.originalSubscriptionClaimId,
|
||||
teamCount: sourceOrg.organisationClaim.teamCount,
|
||||
memberCount: sourceOrg.organisationClaim.memberCount,
|
||||
envelopeItemCount: sourceOrg.organisationClaim.envelopeItemCount,
|
||||
flags: sourceOrg.organisationClaim.flags,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Reset source org's claim to FREE.
|
||||
if (sourceOrg.organisationClaim) {
|
||||
await tx.organisationClaim.update({
|
||||
where: { id: sourceOrg.organisationClaim.id },
|
||||
data: {
|
||||
originalSubscriptionClaimId: INTERNAL_CLAIM_ID.FREE,
|
||||
...createOrganisationClaimUpsertData(internalClaims[INTERNAL_CLAIM_ID.FREE]),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZSwapOrganisationSubscriptionRequestSchema = z.object({
|
||||
sourceOrganisationId: z.string(),
|
||||
targetOrganisationId: z.string(),
|
||||
});
|
||||
|
||||
export const ZSwapOrganisationSubscriptionResponseSchema = z.void();
|
||||
|
||||
export type TSwapOrganisationSubscriptionRequest = z.infer<typeof ZSwapOrganisationSubscriptionRequestSchema>;
|
||||
export type TSwapOrganisationSubscriptionResponse = z.infer<typeof ZSwapOrganisationSubscriptionResponseSchema>;
|
||||
@@ -1,9 +1,8 @@
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { generateDatabaseId } from '@documenso/lib/universal/id';
|
||||
import { getHighestOrganisationRoleInGroup } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
@@ -88,9 +87,7 @@ export const updateOrganisationMemberRoleRoute = adminProcedure
|
||||
(group) => group.organisationRole === currentOrganisationRole,
|
||||
);
|
||||
|
||||
const adminGroup = organisation.groups.find(
|
||||
(group) => group.organisationRole === OrganisationMemberRole.ADMIN,
|
||||
);
|
||||
const adminGroup = organisation.groups.find((group) => group.organisationRole === OrganisationMemberRole.ADMIN);
|
||||
|
||||
if (!currentMemberGroup) {
|
||||
ctx.logger.error({
|
||||
@@ -165,13 +162,9 @@ export const updateOrganisationMemberRoleRoute = adminProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const currentMemberGroup = organisation.groups.find(
|
||||
(group) => group.organisationRole === currentOrganisationRole,
|
||||
);
|
||||
const currentMemberGroup = organisation.groups.find((group) => group.organisationRole === currentOrganisationRole);
|
||||
|
||||
const newMemberGroup = organisation.groups.find(
|
||||
(group) => group.organisationRole === targetRole,
|
||||
);
|
||||
const newMemberGroup = organisation.groups.find((group) => group.organisationRole === targetRole);
|
||||
|
||||
if (!currentMemberGroup) {
|
||||
ctx.logger.error({
|
||||
|
||||
@@ -22,9 +22,5 @@ export const ZUpdateOrganisationMemberRoleRequestSchema = z.object({
|
||||
|
||||
export const ZUpdateOrganisationMemberRoleResponseSchema = z.void();
|
||||
|
||||
export type TUpdateOrganisationMemberRoleRequest = z.infer<
|
||||
typeof ZUpdateOrganisationMemberRoleRequestSchema
|
||||
>;
|
||||
export type TUpdateOrganisationMemberRoleResponse = z.infer<
|
||||
typeof ZUpdateOrganisationMemberRoleResponseSchema
|
||||
>;
|
||||
export type TUpdateOrganisationMemberRoleRequest = z.infer<typeof ZUpdateOrganisationMemberRoleRequestSchema>;
|
||||
export type TUpdateOrganisationMemberRoleResponse = z.infer<typeof ZUpdateOrganisationMemberRoleResponseSchema>;
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { updateRecipient } from '@documenso/lib/server-only/admin/update-recipient';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateRecipientRequestSchema,
|
||||
ZUpdateRecipientResponseSchema,
|
||||
} from './update-recipient.types';
|
||||
import { ZUpdateRecipientRequestSchema, ZUpdateRecipientResponseSchema } from './update-recipient.types';
|
||||
|
||||
export const updateRecipientRoute = adminProcedure
|
||||
.input(ZUpdateRecipientRequestSchema)
|
||||
.output(ZUpdateRecipientResponseSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { id, name, email } = input;
|
||||
const { id, name, email, role } = input;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -18,5 +15,5 @@ export const updateRecipientRoute = adminProcedure
|
||||
},
|
||||
});
|
||||
|
||||
await updateRecipient({ id, name, email });
|
||||
await updateRecipient({ id, name, email, role });
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZUpdateRecipientRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
name: z.string().optional(),
|
||||
email: z.string().email().optional(),
|
||||
email: zEmail().optional(),
|
||||
role: z.enum(['CC', 'SIGNER', 'VIEWER', 'APPROVER', 'ASSISTANT']).optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateRecipientResponseSchema = z.void();
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { upsertSiteSetting } from '@documenso/lib/server-only/site-settings/upsert-site-setting';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateSiteSettingRequestSchema,
|
||||
ZUpdateSiteSettingResponseSchema,
|
||||
} from './update-site-setting.types';
|
||||
import { ZUpdateSiteSettingRequestSchema, ZUpdateSiteSettingResponseSchema } from './update-site-setting.types';
|
||||
|
||||
export const updateSiteSettingRoute = adminProcedure
|
||||
.input(ZUpdateSiteSettingRequestSchema)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZSiteSettingSchema } from '@documenso/lib/server-only/site-settings/schema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZUpdateSiteSettingRequestSchema = ZSiteSettingSchema;
|
||||
|
||||
|
||||
@@ -29,30 +29,25 @@ export const updateSubscriptionClaimRoute = adminProcedure
|
||||
|
||||
const newlyEnabledFlags = getNewTruthyFlags(existingClaim.flags, data.flags);
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.subscriptionClaim.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data,
|
||||
});
|
||||
|
||||
if (Object.keys(newlyEnabledFlags).length > 0) {
|
||||
await jobsClient.triggerJob({
|
||||
name: 'internal.backport-subscription-claims',
|
||||
payload: {
|
||||
subscriptionClaimId: id,
|
||||
flags: newlyEnabledFlags,
|
||||
},
|
||||
});
|
||||
}
|
||||
await prisma.subscriptionClaim.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data,
|
||||
});
|
||||
|
||||
if (Object.keys(newlyEnabledFlags).length > 0) {
|
||||
await jobsClient.triggerJob({
|
||||
name: 'internal.backport-subscription-claims',
|
||||
payload: {
|
||||
subscriptionClaimId: id,
|
||||
flags: newlyEnabledFlags,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function getNewTruthyFlags(
|
||||
a: Partial<TClaimFlags>,
|
||||
b: Partial<TClaimFlags>,
|
||||
): Record<keyof TClaimFlags, true> {
|
||||
function getNewTruthyFlags(a: Partial<TClaimFlags>, b: Partial<TClaimFlags>): Record<keyof TClaimFlags, true> {
|
||||
const flags: { [key in keyof TClaimFlags]?: true } = {};
|
||||
|
||||
for (const key in b) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { Role } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZUpdateUserRequestSchema = z.object({
|
||||
id: z.number().min(1),
|
||||
name: z.string().nullish(),
|
||||
email: z.string().email().optional(),
|
||||
email: zEmail().optional(),
|
||||
roles: z.array(z.nativeEnum(Role)).optional(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user