Files
documenso/packages/lib/utils/teams.ts
Catalin Pit d1eb14ac16 feat: include audit trail log in the completed doc (#1916)
This change allows users to include the audit trail log in the completed
documents; similar to the signing certificate.


https://github.com/user-attachments/assets/d9ae236a-2584-4ad6-b7bc-27b3eb8c74d3

It also solves the issue with the text cutoff.
2025-08-07 11:44:59 +10:00

217 lines
5.8 KiB
TypeScript

import type { OrganisationGlobalSettings, Prisma, TeamGlobalSettings } from '@prisma/client';
import type { TeamGroup } from '@documenso/prisma/generated/types';
import type { TeamMemberRole } from '@documenso/prisma/generated/types';
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
import {
LOWEST_TEAM_ROLE,
TEAM_MEMBER_ROLE_HIERARCHY,
TEAM_MEMBER_ROLE_PERMISSIONS_MAP,
} from '../constants/teams';
import type { TEAM_MEMBER_ROLE_MAP } from '../constants/teams-translations';
/**
* Workaround for E2E tests to not import `msg`.
*/
export enum DocumentSignatureType {
DRAW = 'draw',
TYPE = 'type',
UPLOAD = 'upload',
}
export const formatTeamUrl = (teamUrl: string, baseUrl?: string) => {
const formattedBaseUrl = (baseUrl ?? NEXT_PUBLIC_WEBAPP_URL()).replace(/https?:\/\//, '');
return `${formattedBaseUrl}/t/${teamUrl}`;
};
export const formatDocumentsPath = (teamUrl: string) => {
return `/t/${teamUrl}/documents`;
};
export const formatTemplatesPath = (teamUrl: string) => {
return `/t/${teamUrl}/templates`;
};
/**
* Determines whether a team member can execute a given action.
*
* @param action The action the user is trying to execute.
* @param role The current role of the user.
* @returns Whether the user can execute the action.
*/
export const canExecuteTeamAction = (
action: keyof typeof TEAM_MEMBER_ROLE_PERMISSIONS_MAP,
role: keyof typeof TEAM_MEMBER_ROLE_MAP,
) => {
return TEAM_MEMBER_ROLE_PERMISSIONS_MAP[action].some((i) => i === role);
};
/**
* Compares the provided `currentUserRole` with the provided `roleToCheck` to determine
* whether the `currentUserRole` has permission to modify the `roleToCheck`.
*
* @param currentUserRole Role of the current user
* @param roleToCheck Role of another user to see if the current user can modify
* @returns True if the current user can modify the other user, false otherwise
*/
export const isTeamRoleWithinUserHierarchy = (
currentUserRole: keyof typeof TEAM_MEMBER_ROLE_MAP,
roleToCheck: keyof typeof TEAM_MEMBER_ROLE_MAP,
) => {
return TEAM_MEMBER_ROLE_HIERARCHY[currentUserRole].some((i) => i === roleToCheck);
};
export const getHighestTeamRoleInGroup = (groups: TeamGroup[]): TeamMemberRole => {
let highestTeamRole: TeamMemberRole = LOWEST_TEAM_ROLE;
groups.forEach((group) => {
const currentRolePriority = TEAM_MEMBER_ROLE_HIERARCHY[group.teamRole].length;
const highestTeamRolePriority = TEAM_MEMBER_ROLE_HIERARCHY[highestTeamRole].length;
if (currentRolePriority > highestTeamRolePriority) {
highestTeamRole = group.teamRole;
}
});
return highestTeamRole;
};
export const extractTeamSignatureSettings = (
settings?: {
typedSignatureEnabled: boolean | null;
drawSignatureEnabled: boolean | null;
uploadSignatureEnabled: boolean | null;
} | null,
) => {
if (!settings) {
return [DocumentSignatureType.TYPE, DocumentSignatureType.UPLOAD, DocumentSignatureType.DRAW];
}
const signatureTypes: DocumentSignatureType[] = [];
if (settings.typedSignatureEnabled) {
signatureTypes.push(DocumentSignatureType.TYPE);
}
if (settings.drawSignatureEnabled) {
signatureTypes.push(DocumentSignatureType.DRAW);
}
if (settings.uploadSignatureEnabled) {
signatureTypes.push(DocumentSignatureType.UPLOAD);
}
return signatureTypes;
};
type BuildTeamWhereQueryOptions = {
teamId: number | undefined;
userId: number;
roles?: TeamMemberRole[];
};
export const buildTeamWhereQuery = ({
teamId,
userId,
roles,
}: BuildTeamWhereQueryOptions): Prisma.TeamWhereUniqueInput => {
// Note: Not using inline ternary since typesafety breaks for some reason.
if (!roles) {
return {
id: teamId,
teamGroups: {
some: {
organisationGroup: {
organisationGroupMembers: {
some: {
organisationMember: {
userId,
},
},
},
},
},
},
};
}
return {
id: teamId,
teamGroups: {
some: {
organisationGroup: {
organisationGroupMembers: {
some: {
organisationMember: {
userId,
},
},
},
},
teamRole: {
in: roles,
},
},
},
};
};
/**
* Majority of these are null which lets us inherit from the organisation settings.
*/
export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | 'team'> => {
return {
documentVisibility: null,
documentLanguage: null,
documentTimezone: null,
documentDateFormat: null,
includeSenderDetails: null,
includeSigningCertificate: null,
includeAuditLog: null,
typedSignatureEnabled: null,
uploadSignatureEnabled: null,
drawSignatureEnabled: null,
brandingEnabled: null,
brandingLogo: null,
brandingUrl: null,
brandingCompanyDetails: null,
emailDocumentSettings: null,
emailId: null,
emailReplyTo: null,
// emailReplyToName: null,
};
};
/**
* Derive the final settings for a team.
*
* @param organisationSettings The organisation settings to inherit values from
* @param teamSettings The team settings which can override the organisation settings
*/
export const extractDerivedTeamSettings = (
organisationSettings: Omit<OrganisationGlobalSettings, 'id'>,
teamSettings: Omit<TeamGlobalSettings, 'id'>,
): Omit<OrganisationGlobalSettings, 'id'> => {
const derivedSettings: Omit<OrganisationGlobalSettings, 'id'> = {
...organisationSettings,
};
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
for (const key of Object.keys(derivedSettings) as (keyof typeof derivedSettings)[]) {
const teamValue = teamSettings[key];
if (teamValue !== null) {
// @ts-expect-error Should work
derivedSettings[key] = teamValue;
}
}
return derivedSettings;
};