mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 09:54:51 +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,10 +1,3 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { Team } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { ConfirmTeamEmailTemplate } from '@documenso/email/templates/confirm-team-email';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
@@ -12,6 +5,11 @@ import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createTokenVerification } from '@documenso/lib/utils/token-verification';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import type { Team } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { env } from '../../utils/env';
|
||||
@@ -52,36 +50,35 @@ export const createTeamEmailVerification = async ({
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const existingTeamEmail = await tx.teamEmail.findFirst({
|
||||
where: {
|
||||
email: data.email,
|
||||
},
|
||||
const { token, expiresAt } = createTokenVerification({ hours: 1 });
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const existingTeamEmail = await tx.teamEmail.findFirst({
|
||||
where: {
|
||||
email: data.email,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingTeamEmail) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'Email already taken by another team.',
|
||||
});
|
||||
}
|
||||
|
||||
if (existingTeamEmail) {
|
||||
throw new AppError(AppErrorCode.ALREADY_EXISTS, {
|
||||
message: 'Email already taken by another team.',
|
||||
});
|
||||
}
|
||||
await tx.teamEmailVerification.create({
|
||||
data: {
|
||||
token,
|
||||
expiresAt,
|
||||
email: data.email,
|
||||
name: data.name,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const { token, expiresAt } = createTokenVerification({ hours: 1 });
|
||||
|
||||
await tx.teamEmailVerification.create({
|
||||
data: {
|
||||
token,
|
||||
expiresAt,
|
||||
email: data.email,
|
||||
name: data.name,
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
await sendTeamEmailVerificationEmail(data.email, token, team);
|
||||
},
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
// Send email outside the transaction to avoid holding a connection
|
||||
// open during network I/O.
|
||||
await sendTeamEmailVerificationEmail(data.email, token, team);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -142,9 +139,7 @@ export const sendTeamEmailVerificationEmail = async (email: string, token: strin
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: senderEmail,
|
||||
subject: i18n._(
|
||||
msg`A request to use your email has been initiated by ${team.name} on Documenso`,
|
||||
),
|
||||
subject: i18n._(msg`A request to use your email has been initiated by ${team.name} on Documenso`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import {
|
||||
OrganisationGroupType,
|
||||
OrganisationMemberRole,
|
||||
Prisma,
|
||||
TeamMemberRole,
|
||||
} from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { OrganisationGroupType, OrganisationMemberRole, Prisma, TeamMemberRole } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { IS_BILLING_ENABLED } from '../../constants/app';
|
||||
import {
|
||||
LOWEST_ORGANISATION_ROLE,
|
||||
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP,
|
||||
} from '../../constants/organisations';
|
||||
import { LOWEST_ORGANISATION_ROLE, ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/organisations';
|
||||
import { TEAM_INTERNAL_GROUPS } from '../../constants/teams';
|
||||
import { generateDatabaseId } from '../../universal/id';
|
||||
import { buildOrganisationWhereQuery } from '../../utils/organisations';
|
||||
@@ -56,13 +47,7 @@ export type CreateTeamOptions = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export const createTeam = async ({
|
||||
userId,
|
||||
teamName,
|
||||
teamUrl,
|
||||
organisationId,
|
||||
inheritMembers,
|
||||
}: CreateTeamOptions) => {
|
||||
export const createTeam = async ({ userId, teamName, teamUrl, organisationId, inheritMembers }: CreateTeamOptions) => {
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: buildOrganisationWhereQuery({
|
||||
organisationId,
|
||||
|
||||
@@ -8,10 +8,7 @@ export type DeleteTeamEmailVerificationOptions = {
|
||||
teamId: number;
|
||||
};
|
||||
|
||||
export const deleteTeamEmailVerification = async ({
|
||||
userId,
|
||||
teamId,
|
||||
}: DeleteTeamEmailVerificationOptions) => {
|
||||
export const deleteTeamEmailVerification = async ({ userId, teamId }: DeleteTeamEmailVerificationOptions) => {
|
||||
await prisma.team.findFirstOrThrow({
|
||||
where: buildTeamWhereQuery({
|
||||
teamId,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { TeamEmailRemovedTemplate } from '@documenso/email/templates/team-email-removed';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { env } from '../../utils/env';
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { OrganisationGroupType, type Team } from '@prisma/client';
|
||||
import { uniqueBy } from 'remeda';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { TeamDeleteEmailTemplate } from '@documenso/email/templates/team-delete';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { OrganisationGroupType, type Team } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
import { uniqueBy } from 'remeda';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams';
|
||||
@@ -78,38 +76,35 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => {
|
||||
(member) => member.id,
|
||||
);
|
||||
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
await tx.team.delete({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
});
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.team.delete({
|
||||
where: {
|
||||
id: teamId,
|
||||
},
|
||||
});
|
||||
|
||||
// Purge all internal organisation groups that have no teams.
|
||||
await tx.organisationGroup.deleteMany({
|
||||
where: {
|
||||
type: OrganisationGroupType.INTERNAL_TEAM,
|
||||
teamGroups: {
|
||||
none: {},
|
||||
},
|
||||
// Purge all internal organisation groups that have no teams.
|
||||
await tx.organisationGroup.deleteMany({
|
||||
where: {
|
||||
type: OrganisationGroupType.INTERNAL_TEAM,
|
||||
teamGroups: {
|
||||
none: {},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await jobs.triggerJob({
|
||||
name: 'send.team-deleted.email',
|
||||
payload: {
|
||||
team: {
|
||||
name: team.name,
|
||||
url: team.url,
|
||||
},
|
||||
members: membersToNotify,
|
||||
organisationId: team.organisationId,
|
||||
},
|
||||
});
|
||||
await jobs.triggerJob({
|
||||
name: 'send.team-deleted.email',
|
||||
payload: {
|
||||
team: {
|
||||
name: team.name,
|
||||
url: team.url,
|
||||
},
|
||||
members: membersToNotify,
|
||||
organisationId: team.organisationId,
|
||||
},
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
type SendTeamDeleteEmailOptions = {
|
||||
@@ -118,11 +113,7 @@ type SendTeamDeleteEmailOptions = {
|
||||
organisationId: string;
|
||||
};
|
||||
|
||||
export const sendTeamDeleteEmail = async ({
|
||||
email,
|
||||
team,
|
||||
organisationId,
|
||||
}: SendTeamDeleteEmailOptions) => {
|
||||
export const sendTeamDeleteEmail = async ({ email, team, organisationId }: SendTeamDeleteEmailOptions) => {
|
||||
const template = createElement(TeamDeleteEmailTemplate, {
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
baseUrl: NEXT_PUBLIC_WEBAPP_URL(),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { OrganisationMember } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { match, P } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
@@ -144,14 +143,10 @@ export const findTeamMembers = async ({
|
||||
avatarImageId: member.user.avatarImageId,
|
||||
// Filter teamGroups to only include the current team
|
||||
teamRole: getHighestTeamRoleInGroup(
|
||||
member.organisationGroupMembers.flatMap(({ group }) =>
|
||||
group.teamGroups.filter((tg) => tg.teamId === teamId),
|
||||
),
|
||||
member.organisationGroupMembers.flatMap(({ group }) => group.teamGroups.filter((tg) => tg.teamId === teamId)),
|
||||
),
|
||||
teamRoleGroupType: member.organisationGroupMembers[0].group.type,
|
||||
organisationRole: getHighestOrganisationRoleInGroup(
|
||||
member.organisationGroupMembers.flatMap(({ group }) => group),
|
||||
),
|
||||
organisationRole: getHighestOrganisationRoleInGroup(member.organisationGroupMembers.flatMap(({ group }) => group)),
|
||||
}));
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Team } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
import { getHighestTeamRoleInGroup } from '../../utils/teams';
|
||||
|
||||
|
||||
@@ -83,10 +83,7 @@ type GetMemberOrganisationRoleOptions = {
|
||||
/**
|
||||
* Returns the highest Organisation of a given organisation member
|
||||
*/
|
||||
export const getMemberOrganisationRole = async ({
|
||||
organisationId,
|
||||
reference,
|
||||
}: GetMemberOrganisationRoleOptions) => {
|
||||
export const getMemberOrganisationRole = async ({ organisationId, reference }: GetMemberOrganisationRoleOptions) => {
|
||||
const organisation = await prisma.organisation.findFirst({
|
||||
where: {
|
||||
id: organisationId,
|
||||
|
||||
@@ -23,13 +23,7 @@ export const getTeamEmailByEmail = async ({ email }: GetTeamEmailByEmailOptions)
|
||||
});
|
||||
};
|
||||
|
||||
export const getTeamWithEmail = async ({
|
||||
userId,
|
||||
teamUrl,
|
||||
}: {
|
||||
userId: number;
|
||||
teamUrl: string;
|
||||
}) => {
|
||||
export const getTeamWithEmail = async ({ userId, teamUrl }: { userId: number; teamUrl: string }) => {
|
||||
return await prisma.team.findFirstOrThrow({
|
||||
where: {
|
||||
...buildTeamWhereQuery({ teamId: undefined, userId }),
|
||||
|
||||
@@ -13,10 +13,7 @@ export type GetTeamMembersOptions = {
|
||||
/**
|
||||
* Get all team members for a given team.
|
||||
*/
|
||||
export const getTeamMembers = async ({
|
||||
userId,
|
||||
teamId,
|
||||
}: GetTeamMembersOptions): Promise<TGetTeamMembersResponse> => {
|
||||
export const getTeamMembers = async ({ userId, teamId }: GetTeamMembersOptions): Promise<TGetTeamMembersResponse> => {
|
||||
const teamMembers = await prisma.organisationMember.findMany({
|
||||
where: {
|
||||
organisationGroupMembers: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { TeamProfile } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { TeamProfile } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
@@ -39,6 +39,8 @@ export const getTeamSettings = async ({ userId, teamId }: GetTeamSettingsOptions
|
||||
teamSettings.brandingLogo = organisationSettings.brandingLogo;
|
||||
teamSettings.brandingUrl = organisationSettings.brandingUrl;
|
||||
teamSettings.brandingCompanyDetails = organisationSettings.brandingCompanyDetails;
|
||||
teamSettings.brandingColors = organisationSettings.brandingColors;
|
||||
teamSettings.brandingCss = organisationSettings.brandingCss;
|
||||
}
|
||||
|
||||
return extractDerivedTeamSettings(organisationSettings, teamSettings);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import {
|
||||
buildTeamWhereQuery,
|
||||
extractDerivedTeamSettings,
|
||||
getHighestTeamRoleInGroup,
|
||||
} from '../../utils/teams';
|
||||
import { buildTeamWhereQuery, extractDerivedTeamSettings, getHighestTeamRoleInGroup } from '../../utils/teams';
|
||||
|
||||
export type GetTeamByIdOptions = {
|
||||
userId: number;
|
||||
@@ -39,13 +35,7 @@ export const getTeamByUrl = async ({ userId, teamUrl }: GetTeamByUrlOptions) =>
|
||||
/**
|
||||
* Get a team by its ID or URL.
|
||||
*/
|
||||
export const getTeam = async ({
|
||||
teamReference,
|
||||
userId,
|
||||
}: {
|
||||
teamReference: number | string;
|
||||
userId: number;
|
||||
}) => {
|
||||
export const getTeam = async ({ teamReference, userId }: { teamReference: number | string; userId: number }) => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: {
|
||||
...buildTeamWhereQuery({ teamId: undefined, userId }),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/generated/types';
|
||||
import { TeamSchema } from '@documenso/prisma/generated/zod/modelSchema/TeamSchema';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { buildTeamWhereQuery, getHighestTeamRoleInGroup } from '../../utils/teams';
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { TeamGroup } from '@prisma/client';
|
||||
|
||||
export type GetUserTeamIdsOptions = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pre-resolve all team groups a user has access to via their organisation group memberships,
|
||||
* keyed by team ID.
|
||||
*
|
||||
* This is significantly cheaper than joining team groups inline in a Prisma `findMany`
|
||||
* because it avoids deep EXISTS subqueries and redundant LEFT JOINs per row.
|
||||
*/
|
||||
export const getUserTeamGroups = async ({ userId }: GetUserTeamIdsOptions): Promise<Map<number, TeamGroup[]>> => {
|
||||
const teamGroups = await prisma.teamGroup.findMany({
|
||||
where: {
|
||||
organisationGroup: {
|
||||
organisationGroupMembers: {
|
||||
some: {
|
||||
organisationMember: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const map = new Map<number, TeamGroup[]>();
|
||||
|
||||
for (const tg of teamGroups) {
|
||||
const existing = map.get(tg.teamId);
|
||||
|
||||
if (existing) {
|
||||
existing.push(tg);
|
||||
} else {
|
||||
map.set(tg.teamId, [tg]);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
@@ -35,30 +35,27 @@ export const resendTeamEmailVerification = async ({
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const { emailVerification } = team;
|
||||
const { emailVerification } = team;
|
||||
|
||||
if (!emailVerification) {
|
||||
throw new AppError('VerificationNotFound', {
|
||||
message: 'No team email verification exists for this team.',
|
||||
});
|
||||
}
|
||||
if (!emailVerification) {
|
||||
throw new AppError('VerificationNotFound', {
|
||||
message: 'No team email verification exists for this team.',
|
||||
});
|
||||
}
|
||||
|
||||
const { token, expiresAt } = createTokenVerification({ hours: 1 });
|
||||
const { token, expiresAt } = createTokenVerification({ hours: 1 });
|
||||
|
||||
await tx.teamEmailVerification.update({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
data: {
|
||||
token,
|
||||
expiresAt,
|
||||
},
|
||||
});
|
||||
|
||||
await sendTeamEmailVerificationEmail(emailVerification.email, token, team);
|
||||
await prisma.teamEmailVerification.update({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
data: {
|
||||
token,
|
||||
expiresAt,
|
||||
},
|
||||
});
|
||||
|
||||
// Send email outside any transaction to avoid holding a connection
|
||||
// open during network I/O.
|
||||
await sendTeamEmailVerificationEmail(emailVerification.email, token, team);
|
||||
};
|
||||
|
||||
@@ -12,11 +12,7 @@ export type UpdateTeamEmailOptions = {
|
||||
};
|
||||
};
|
||||
|
||||
export const updateTeamEmail = async ({
|
||||
userId,
|
||||
teamId,
|
||||
data,
|
||||
}: UpdateTeamEmailOptions): Promise<void> => {
|
||||
export const updateTeamEmail = async ({ userId, teamId, data }: UpdateTeamEmailOptions): Promise<void> => {
|
||||
const team = await prisma.team.findFirst({
|
||||
where: buildTeamWhereQuery({
|
||||
teamId,
|
||||
|
||||
@@ -11,11 +11,7 @@ export type UpdatePublicProfileOptions = {
|
||||
};
|
||||
};
|
||||
|
||||
export const updateTeamPublicProfile = async ({
|
||||
userId,
|
||||
teamId,
|
||||
data,
|
||||
}: UpdatePublicProfileOptions) => {
|
||||
export const updateTeamPublicProfile = async ({ userId, teamId, data }: UpdatePublicProfileOptions) => {
|
||||
return await prisma.team.update({
|
||||
where: buildTeamWhereQuery({ teamId, userId }),
|
||||
data: {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user