mirror of
https://github.com/documenso/documenso.git
synced 2026-07-22 16:03:39 +10:00
Merge branch 'main' into feature/pdf-placeholder-selection-fields
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZOrganisationNameSchema } from '../organisation-router/create-organisation.types';
|
||||
|
||||
export const ZCreateAdminOrganisationRequestSchema = z.object({
|
||||
ownerUserId: z.number(),
|
||||
data: z.object({
|
||||
name: ZOrganisationNameSchema,
|
||||
name: ZNameSchema,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { ZClaimFlagsSchema, ZRateLimitArraySchema } from '@documenso/lib/types/subscription';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateSubscriptionClaimRequestSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
name: ZNameSchema,
|
||||
teamCount: z.number().int().min(0),
|
||||
memberCount: z.number().int().min(0),
|
||||
envelopeItemCount: z.number().int().min(1),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ZNameSchema } from '@documenso/lib/constants/auth';
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateUserRequestSchema = z.object({
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ZEmailTransportConfigSchema } from '@documenso/lib/server-only/email/email-transport-config';
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateEmailTransportRequestSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
fromName: z.string().min(1),
|
||||
name: ZNameSchema,
|
||||
fromName: ZNameSchema,
|
||||
fromAddress: z.string().email(),
|
||||
config: ZEmailTransportConfigSchema,
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ZSmtpApiConfigSchema,
|
||||
ZSmtpAuthConfigSchema,
|
||||
} from '@documenso/lib/server-only/email/email-transport-config';
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Reuses the canonical transport config schemas, but relaxes the secret field so
|
||||
@@ -21,8 +22,8 @@ const ZUpdateConfigSchema = z.discriminatedUnion('type', [
|
||||
export const ZUpdateEmailTransportRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
data: z.object({
|
||||
name: z.string().min(1),
|
||||
fromName: z.string().min(1),
|
||||
name: ZNameSchema,
|
||||
fromName: ZNameSchema,
|
||||
fromAddress: z.string().email(),
|
||||
config: ZUpdateConfigSchema,
|
||||
}),
|
||||
|
||||
@@ -30,6 +30,7 @@ export const getAdminTeamRoute = adminProcedure
|
||||
name: true,
|
||||
url: true,
|
||||
ownerUserId: true,
|
||||
organisationGlobalSettings: true,
|
||||
},
|
||||
},
|
||||
teamEmail: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { OrganisationMemberRoleSchema } from '@documenso/prisma/generated/zod/inputTypeSchemas/OrganisationMemberRoleSchema';
|
||||
import { TeamMemberRoleSchema } from '@documenso/prisma/generated/zod/inputTypeSchemas/TeamMemberRoleSchema';
|
||||
import OrganisationGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationGlobalSettingsSchema';
|
||||
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';
|
||||
@@ -19,6 +20,8 @@ export const ZGetAdminTeamResponseSchema = TeamSchema.extend({
|
||||
name: true,
|
||||
url: true,
|
||||
ownerUserId: true,
|
||||
}).extend({
|
||||
organisationGlobalSettings: OrganisationGlobalSettingsSchema,
|
||||
}),
|
||||
teamEmail: TeamEmailSchema.nullable(),
|
||||
teamGlobalSettings: TeamGlobalSettingsSchema.nullable(),
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
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(),
|
||||
name: ZNameSchema.optional(),
|
||||
url: ZTeamUrlSchema.optional(),
|
||||
claims: ZCreateSubscriptionClaimRequestSchema.pick({
|
||||
teamCount: true,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
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(),
|
||||
name: ZNameSchema.nullish(),
|
||||
email: zEmail().optional(),
|
||||
roles: z.array(z.nativeEnum(Role)).optional(),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateApiTokenRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
tokenName: z.string().min(3, { message: 'The token name should be 3 characters or longer' }),
|
||||
tokenName: ZNameSchema,
|
||||
expirationDate: z.string().nullable(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { ZRegistrationResponseJSONSchema } from '@documenso/lib/types/webauthn';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreatePasskeyRequestSchema = z.object({
|
||||
passkeyName: z.string().trim().min(1),
|
||||
passkeyName: ZNameSchema,
|
||||
verificationResponse: ZRegistrationResponseJSONSchema,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZUpdatePasskeyRequestSchema = z.object({
|
||||
passkeyId: z.string().trim().min(1),
|
||||
name: z.string().trim().min(1),
|
||||
name: ZNameSchema,
|
||||
});
|
||||
|
||||
export const ZUpdatePasskeyResponseSchema = z.void();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCreateOrganisationEmailRequestSchema = z.object({
|
||||
emailDomainId: z.string(),
|
||||
emailName: z.string().min(1).max(100),
|
||||
emailName: ZNameSchema,
|
||||
email: zEmail().toLowerCase(),
|
||||
|
||||
// This does not need to be validated to be part of the domain.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ZFolderTypeSchema } from '@documenso/lib/types/folder-type';
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { DocumentVisibility } from '@documenso/prisma/generated/types';
|
||||
import FolderSchema from '@documenso/prisma/generated/zod/modelSchema/FolderSchema';
|
||||
@@ -42,7 +43,7 @@ const ZFolderParentIdSchema = z
|
||||
.describe('The folder ID to place this folder within. Leave empty to place folder at the root level.');
|
||||
|
||||
export const ZCreateFolderRequestSchema = z.object({
|
||||
name: z.string(),
|
||||
name: ZNameSchema,
|
||||
parentId: ZFolderParentIdSchema.optional(),
|
||||
type: ZFolderTypeSchema.optional(),
|
||||
});
|
||||
@@ -52,7 +53,7 @@ export const ZCreateFolderResponseSchema = ZFolderSchema;
|
||||
export const ZUpdateFolderRequestSchema = z.object({
|
||||
folderId: z.string().describe('The ID of the folder to update'),
|
||||
data: z.object({
|
||||
name: z.string().optional().describe('The name of the folder'),
|
||||
name: ZNameSchema.optional().describe('The name of the folder'),
|
||||
parentId: ZFolderParentIdSchema.optional().nullable(),
|
||||
visibility: z.nativeEnum(DocumentVisibility).optional().describe('The visibility of the folder'),
|
||||
pinned: z.boolean().optional().describe('Whether the folder should be pinned'),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -14,7 +15,7 @@ import { z } from 'zod';
|
||||
export const ZCreateOrganisationGroupRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
organisationRole: z.nativeEnum(OrganisationMemberRole),
|
||||
name: z.string().max(100),
|
||||
name: ZNameSchema,
|
||||
memberIds: z.array(z.string()),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
// export const createOrganisationMeta: TrpcOpenApiMeta = {
|
||||
@@ -10,13 +11,8 @@ import { z } from 'zod';
|
||||
// },
|
||||
// };
|
||||
|
||||
export const ZOrganisationNameSchema = z
|
||||
.string()
|
||||
.min(3, { message: 'Minimum 3 characters' })
|
||||
.max(50, { message: 'Maximum 50 characters' });
|
||||
|
||||
export const ZCreateOrganisationRequestSchema = z.object({
|
||||
name: ZOrganisationNameSchema,
|
||||
name: ZNameSchema,
|
||||
priceId: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { getOrganisationsRoute } from './get-organisations';
|
||||
import { leaveOrganisationRoute } from './leave-organisation';
|
||||
import { resendOrganisationMemberInviteRoute } from './resend-organisation-member-invite';
|
||||
import { updateOrganisationRoute } from './update-organisation';
|
||||
import { updateOrganisationBrandingLogoRoute } from './update-organisation-branding-logo';
|
||||
import { updateOrganisationGroupRoute } from './update-organisation-group';
|
||||
import { updateOrganisationMemberRoute } from './update-organisation-members';
|
||||
import { updateOrganisationSettingsRoute } from './update-organisation-settings';
|
||||
@@ -55,6 +56,7 @@ export const organisationRouter = router({
|
||||
},
|
||||
settings: {
|
||||
update: updateOrganisationSettingsRoute,
|
||||
updateBrandingLogo: updateOrganisationBrandingLogoRoute,
|
||||
},
|
||||
internal: {
|
||||
getOrganisationSession: getOrganisationSessionRoute,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
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 { buildBrandingLogoData } from '@documenso/lib/server-only/branding/store-branding-logo';
|
||||
import { getOrganisationClaim } from '@documenso/lib/server-only/organisation/get-organisation-claims';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateOrganisationBrandingLogoRequestSchema,
|
||||
ZUpdateOrganisationBrandingLogoResponseSchema,
|
||||
} from './update-organisation-branding-logo.types';
|
||||
|
||||
export const updateOrganisationBrandingLogoRoute = authenticatedProcedure
|
||||
.input(ZUpdateOrganisationBrandingLogoRequestSchema)
|
||||
.output(ZUpdateOrganisationBrandingLogoResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { user } = ctx;
|
||||
const { payload, brandingLogo } = input;
|
||||
const { organisationId } = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
organisationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
userId: user.id,
|
||||
roles: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!organisation) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have permission to update this organisation.',
|
||||
});
|
||||
}
|
||||
|
||||
// Setting a logo requires the custom-branding entitlement; clearing it is
|
||||
// always allowed so a downgraded organisation can still remove its logo.
|
||||
if (brandingLogo && IS_BILLING_ENABLED()) {
|
||||
const claim = await getOrganisationClaim({ organisationId });
|
||||
|
||||
if (claim.flags?.allowCustomBranding !== true) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Your plan does not allow custom branding.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const brandingLogoValue = brandingLogo ? await buildBrandingLogoData(brandingLogo) : '';
|
||||
|
||||
await prisma.organisation.update({
|
||||
where: {
|
||||
id: organisation.id,
|
||||
},
|
||||
data: {
|
||||
organisationGlobalSettings: {
|
||||
update: {
|
||||
brandingLogo: brandingLogoValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
import { zfd } from 'zod-form-data';
|
||||
|
||||
import { zfdBrandingImageFile, zodFormData } from '../../utils/zod-form-data';
|
||||
|
||||
export const ZUpdateOrganisationBrandingLogoRequestSchema = zodFormData({
|
||||
payload: zfd.json(
|
||||
z.object({
|
||||
organisationId: z.string(),
|
||||
}),
|
||||
),
|
||||
brandingLogo: zfdBrandingImageFile().optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateOrganisationBrandingLogoResponseSchema = z.void();
|
||||
|
||||
export type TUpdateOrganisationBrandingLogoRequest = z.infer<typeof ZUpdateOrganisationBrandingLogoRequestSchema>;
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -14,7 +15,7 @@ import { z } from 'zod';
|
||||
|
||||
export const ZUpdateOrganisationGroupRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().nullable().optional(),
|
||||
name: ZNameSchema.nullable().optional(),
|
||||
organisationRole: z.nativeEnum(OrganisationMemberRole).optional(),
|
||||
memberIds: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
@@ -44,7 +44,6 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
brandingColors,
|
||||
@@ -174,7 +173,6 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
brandingColors: normalizedBrandingColors === null ? Prisma.DbNull : normalizedBrandingColors,
|
||||
|
||||
@@ -32,7 +32,6 @@ export const ZUpdateOrganisationSettingsRequestSchema = z.object({
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled: z.boolean().optional(),
|
||||
brandingLogo: z.string().optional(),
|
||||
brandingUrl: z.string().optional(),
|
||||
brandingCompanyDetails: z.string().optional(),
|
||||
brandingColors: ZCssVarsSchema.nullish(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ZNameSchema } from '@documenso/lib/constants/auth';
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZFindUserSecurityAuditLogsSchema = z.object({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
import { ZTeamNameSchema, ZTeamUrlSchema } from './schema';
|
||||
import { ZTeamUrlSchema } from './schema';
|
||||
|
||||
// export const createTeamMeta: TrpcOpenApiMeta = {
|
||||
// openapi: {
|
||||
@@ -13,7 +14,7 @@ import { ZTeamNameSchema, ZTeamUrlSchema } from './schema';
|
||||
|
||||
export const ZCreateTeamRequestSchema = z.object({
|
||||
organisationId: z.string(),
|
||||
teamName: ZTeamNameSchema,
|
||||
teamName: ZNameSchema,
|
||||
teamUrl: ZTeamUrlSchema,
|
||||
inheritMembers: z
|
||||
.boolean()
|
||||
|
||||
@@ -53,6 +53,15 @@ export const deleteTeamGroupRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
// You cannot delete internal team groups. These are the system-managed
|
||||
// admin/manager/member groups that back the team's role-based access, and
|
||||
// deleting them would silently strip team members of their access.
|
||||
if (group.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not allowed to delete internal team groups',
|
||||
});
|
||||
}
|
||||
|
||||
// You cannot delete internal organisation groups.
|
||||
// The only exception is deleting the "member" organisation group which is used to allow
|
||||
// all organisation members to access a team.
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ZUpdateTeamEmailMutationSchema,
|
||||
} from './schema';
|
||||
import { updateTeamRoute } from './update-team';
|
||||
import { updateTeamBrandingLogoRoute } from './update-team-branding-logo';
|
||||
import { updateTeamGroupRoute } from './update-team-group';
|
||||
import { updateTeamMemberRoute } from './update-team-member';
|
||||
import { updateTeamSettingsRoute } from './update-team-settings';
|
||||
@@ -50,6 +51,7 @@ export const teamRouter = router({
|
||||
},
|
||||
settings: {
|
||||
update: updateTeamSettingsRoute,
|
||||
updateBrandingLogo: updateTeamBrandingLogoRoute,
|
||||
},
|
||||
|
||||
// Old routes (to be migrated)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { URL_PATTERN, ZNameSchema } from '@documenso/lib/constants/auth';
|
||||
import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams';
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
@@ -32,15 +32,6 @@ export const ZTeamUrlSchema = z
|
||||
message: 'This URL is already in use.',
|
||||
});
|
||||
|
||||
export const ZTeamNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(3, { message: 'Team name must be at least 3 characters long.' })
|
||||
.max(30, { message: 'Team name must not exceed 30 characters.' })
|
||||
.refine((value) => !URL_PATTERN.test(value), {
|
||||
message: 'Team name cannot contain URLs.',
|
||||
});
|
||||
|
||||
export const ZCreateTeamEmailVerificationMutationSchema = z.object({
|
||||
teamId: z.number(),
|
||||
name: ZNameSchema,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { buildBrandingLogoData } from '@documenso/lib/server-only/branding/store-branding-logo';
|
||||
import { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims';
|
||||
import { buildTeamWhereQuery } from '@documenso/lib/utils/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
import {
|
||||
ZUpdateTeamBrandingLogoRequestSchema,
|
||||
ZUpdateTeamBrandingLogoResponseSchema,
|
||||
} from './update-team-branding-logo.types';
|
||||
|
||||
export const updateTeamBrandingLogoRoute = authenticatedProcedure
|
||||
.input(ZUpdateTeamBrandingLogoRequestSchema)
|
||||
.output(ZUpdateTeamBrandingLogoResponseSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { user } = ctx;
|
||||
const { payload, brandingLogo } = input;
|
||||
const { teamId } = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({
|
||||
teamId,
|
||||
userId: user.id,
|
||||
roles: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM'],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You do not have permission to update this team.',
|
||||
});
|
||||
}
|
||||
|
||||
// Setting a logo requires the custom-branding entitlement; clearing it is
|
||||
// always allowed so a downgraded team can still remove its logo.
|
||||
if (brandingLogo && IS_BILLING_ENABLED()) {
|
||||
const claim = await getOrganisationClaimByTeamId({ teamId });
|
||||
|
||||
if (claim.flags?.allowCustomBranding !== true) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'Your plan does not allow custom branding.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const brandingLogoValue = brandingLogo ? await buildBrandingLogoData(brandingLogo) : '';
|
||||
|
||||
await prisma.team.update({
|
||||
where: {
|
||||
id: team.id,
|
||||
},
|
||||
data: {
|
||||
teamGlobalSettings: {
|
||||
update: {
|
||||
brandingLogo: brandingLogoValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
import { zfd } from 'zod-form-data';
|
||||
|
||||
import { zfdBrandingImageFile, zodFormData } from '../../utils/zod-form-data';
|
||||
|
||||
export const ZUpdateTeamBrandingLogoRequestSchema = zodFormData({
|
||||
payload: zfd.json(
|
||||
z.object({
|
||||
teamId: z.number(),
|
||||
}),
|
||||
),
|
||||
brandingLogo: zfdBrandingImageFile().optional(),
|
||||
});
|
||||
|
||||
export const ZUpdateTeamBrandingLogoResponseSchema = z.void();
|
||||
|
||||
export type TUpdateTeamBrandingLogoRequest = z.infer<typeof ZUpdateTeamBrandingLogoRequestSchema>;
|
||||
@@ -45,9 +45,12 @@ export const updateTeamGroupRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
if (teamGroup.organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION) {
|
||||
if (
|
||||
teamGroup.organisationGroup.type === OrganisationGroupType.INTERNAL_ORGANISATION ||
|
||||
teamGroup.organisationGroup.type === OrganisationGroupType.INTERNAL_TEAM
|
||||
) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'You are not allowed to update internal organisation groups',
|
||||
message: 'You are not allowed to update internal groups',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
brandingColors,
|
||||
@@ -176,7 +175,6 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled,
|
||||
brandingLogo,
|
||||
brandingUrl,
|
||||
brandingCompanyDetails,
|
||||
brandingColors: normalizedBrandingColors === null ? Prisma.DbNull : normalizedBrandingColors,
|
||||
|
||||
@@ -35,7 +35,6 @@ export const ZUpdateTeamSettingsRequestSchema = z.object({
|
||||
|
||||
// Branding related settings.
|
||||
brandingEnabled: z.boolean().nullish(),
|
||||
brandingLogo: z.string().nullish(),
|
||||
brandingUrl: z.string().nullish(),
|
||||
brandingCompanyDetails: z.string().nullish(),
|
||||
brandingColors: ZCssVarsSchema.nullish(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ZNameSchema } from '@documenso/lib/types/name';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZTeamNameSchema, ZTeamUrlSchema } from './schema';
|
||||
import { ZTeamUrlSchema } from './schema';
|
||||
|
||||
export const MAX_PROFILE_BIO_LENGTH = 256;
|
||||
|
||||
@@ -19,7 +20,7 @@ export const MAX_PROFILE_BIO_LENGTH = 256;
|
||||
export const ZUpdateTeamRequestSchema = z.object({
|
||||
teamId: z.number(),
|
||||
data: z.object({
|
||||
name: ZTeamNameSchema.optional(),
|
||||
name: ZNameSchema.optional(),
|
||||
url: ZTeamUrlSchema.optional(),
|
||||
profileBio: z
|
||||
.string()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isPrivateUrl } from '@documenso/lib/server-only/webhooks/is-private-url';
|
||||
import { URL_PATTERN } from '@documenso/lib/types/name';
|
||||
import { WebhookTriggerEvents } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -7,6 +8,13 @@ export const ZWebhookUrlSchema = z
|
||||
.url()
|
||||
.refine((url) => !isPrivateUrl(url), {
|
||||
message: 'Webhook URL cannot point to a private or loopback address',
|
||||
})
|
||||
/*
|
||||
* Without this, values like "foo: bar" would be valid URLs.
|
||||
* Keep the same error message as the zod url() validator.
|
||||
*/
|
||||
.refine((value) => URL_PATTERN.test(value), {
|
||||
message: 'Invalid url',
|
||||
});
|
||||
|
||||
export const ZCreateWebhookRequestSchema = z.object({
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import {
|
||||
BRANDING_LOGO_ALLOWED_TYPES,
|
||||
BRANDING_LOGO_MAX_SIZE_BYTES,
|
||||
BRANDING_LOGO_MAX_SIZE_MB,
|
||||
} from '@documenso/lib/constants/branding';
|
||||
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
|
||||
import type { ZodRawShape } from 'zod';
|
||||
import z from 'zod';
|
||||
@@ -17,6 +22,21 @@ export const zfdFile = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A `zfd.file()` schema constrained to branding-logo images: size-limited and
|
||||
* restricted to a MIME allowlist. Use for server-side branding logo uploads.
|
||||
*/
|
||||
export const zfdBrandingImageFile = () => {
|
||||
return zfd
|
||||
.file()
|
||||
.refine((file) => file.size <= BRANDING_LOGO_MAX_SIZE_BYTES, {
|
||||
message: `File cannot be larger than ${BRANDING_LOGO_MAX_SIZE_MB}MB`,
|
||||
})
|
||||
.refine((file) => BRANDING_LOGO_ALLOWED_TYPES.includes(file.type), {
|
||||
message: 'File must be a JPG, PNG, or WebP image',
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This helper takes the place of the `z.object` at the root of your schema.
|
||||
* It wraps your schema in a `z.preprocess` that extracts all the data out of a `FormData`
|
||||
|
||||
Reference in New Issue
Block a user