mirror of
https://github.com/documenso/documenso.git
synced 2026-07-20 15:06:14 +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,6 +1,5 @@
|
||||
import { EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, type Prisma } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
@@ -10,11 +9,7 @@ export interface AdminFindDocumentsOptions {
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
export const adminFindDocuments = async ({
|
||||
query,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
}: AdminFindDocumentsOptions) => {
|
||||
export const adminFindDocuments = async ({ query, page = 1, perPage = 10 }: AdminFindDocumentsOptions) => {
|
||||
let termFilters: Prisma.EnvelopeWhereInput | undefined = !query
|
||||
? undefined
|
||||
: {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType, RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
import type { FindResultResponse } from '../../types/search-params';
|
||||
|
||||
export type AdminUnsealedDocument = {
|
||||
id: string;
|
||||
secondaryId: string;
|
||||
title: string;
|
||||
status: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
userId: number;
|
||||
teamId: number;
|
||||
ownerName: string | null;
|
||||
ownerEmail: string;
|
||||
lastSignedAt: Date | null;
|
||||
};
|
||||
|
||||
export type AdminFindUnsealedDocumentsOptions = {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
|
||||
export const adminFindUnsealedDocuments = async ({
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
}: AdminFindUnsealedDocumentsOptions): Promise<FindResultResponse<AdminUnsealedDocument[]>> => {
|
||||
const offset = Math.max(page - 1, 0) * perPage;
|
||||
|
||||
const baseQuery = kyselyPrisma.$kysely
|
||||
.selectFrom('Envelope')
|
||||
.where('Envelope.status', '=', sql.lit(DocumentStatus.PENDING))
|
||||
.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
||||
.where('Envelope.deletedAt', 'is', null)
|
||||
// Must have at least one recipient.
|
||||
.where((eb) => eb.exists(eb.selectFrom('Recipient').whereRef('Recipient.envelopeId', '=', 'Envelope.id')))
|
||||
// Document is ready to seal: all recipients are SIGNED/CC, or any recipient REJECTED.
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
// Case 1: All recipients are either SIGNED or CC.
|
||||
eb.not(
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Recipient')
|
||||
.whereRef('Recipient.envelopeId', '=', 'Envelope.id')
|
||||
.where('Recipient.signingStatus', '!=', sql.lit(SigningStatus.SIGNED))
|
||||
.where('Recipient.role', '!=', sql.lit(RecipientRole.CC)),
|
||||
),
|
||||
),
|
||||
// Case 2: Any recipient has rejected.
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Recipient')
|
||||
.whereRef('Recipient.envelopeId', '=', 'Envelope.id')
|
||||
.where('Recipient.signingStatus', '=', sql.lit(SigningStatus.REJECTED)),
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
const [data, countResult] = await Promise.all([
|
||||
baseQuery
|
||||
.innerJoin('User', 'User.id', 'Envelope.userId')
|
||||
.select([
|
||||
'Envelope.id',
|
||||
'Envelope.secondaryId',
|
||||
'Envelope.title',
|
||||
'Envelope.status',
|
||||
'Envelope.createdAt',
|
||||
'Envelope.updatedAt',
|
||||
'Envelope.userId',
|
||||
'Envelope.teamId',
|
||||
'User.name as ownerName',
|
||||
'User.email as ownerEmail',
|
||||
])
|
||||
.select((eb) =>
|
||||
eb
|
||||
.selectFrom('Recipient')
|
||||
.whereRef('Recipient.envelopeId', '=', 'Envelope.id')
|
||||
.select(sql<Date>`max("Recipient"."signedAt")`.as('lastSignedAt'))
|
||||
.as('lastSignedAt'),
|
||||
)
|
||||
.orderBy('Envelope.createdAt', 'desc')
|
||||
.limit(perPage)
|
||||
.offset(offset)
|
||||
.execute(),
|
||||
baseQuery.select(({ fn }) => [fn.countAll().as('count')]).execute(),
|
||||
]);
|
||||
|
||||
const count = Number(countResult[0]?.count ?? 0);
|
||||
|
||||
return {
|
||||
data: data as unknown as AdminUnsealedDocument[],
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
};
|
||||
};
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentStatus, SendStatus } from '@prisma/client';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentStatus, SendStatus } from '@prisma/client';
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n-server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
@@ -23,10 +21,7 @@ export type AdminSuperDeleteDocumentOptions = {
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
|
||||
export const adminSuperDeleteDocument = async ({
|
||||
envelopeId,
|
||||
requestMetadata,
|
||||
}: AdminSuperDeleteDocumentOptions) => {
|
||||
export const adminSuperDeleteDocument = async ({ envelopeId, requestMetadata }: AdminSuperDeleteDocumentOptions) => {
|
||||
const envelope = await prisma.envelope.findUnique({
|
||||
where: {
|
||||
id: envelopeId,
|
||||
@@ -61,20 +56,12 @@ export const adminSuperDeleteDocument = async ({
|
||||
|
||||
const { status, user } = envelope;
|
||||
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(
|
||||
envelope.documentMeta,
|
||||
).documentDeleted;
|
||||
const isDocumentDeletedEmailEnabled = extractDerivedDocumentEmailSettings(envelope.documentMeta).documentDeleted;
|
||||
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) =>
|
||||
isRecipientEmailValidForSending(recipient),
|
||||
);
|
||||
const recipientsToNotify = envelope.recipients.filter((recipient) => isRecipientEmailValidForSending(recipient));
|
||||
|
||||
// if the document is pending, send cancellation emails to all recipients
|
||||
if (
|
||||
status === DocumentStatus.PENDING &&
|
||||
recipientsToNotify.length > 0 &&
|
||||
isDocumentDeletedEmailEnabled
|
||||
) {
|
||||
if (status === DocumentStatus.PENDING && recipientsToNotify.length > 0 && isDocumentDeletedEmailEnabled) {
|
||||
await Promise.all(
|
||||
recipientsToNotify.map(async (recipient) => {
|
||||
if (recipient.sendStatus !== SendStatus.SENT) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
export const getDocumentStats = async () => {
|
||||
const counts = await prisma.envelope.groupBy({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import type { EnvelopeIdOptions } from '../../utils/envelope';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { DocumentStatus } from '@prisma/client';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import type { DateRange } from '@documenso/lib/types/search-params';
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import type { DocumentStatus } from '@prisma/client';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
export type OrganisationSummary = {
|
||||
totalTeams: number;
|
||||
@@ -260,10 +259,7 @@ async function getDocumentInsights(
|
||||
|
||||
countQuery = countQuery.select(({ fn }) => [fn.countAll().as('count')]);
|
||||
|
||||
const [documents, countResult] = await Promise.all([
|
||||
documentsQuery.execute(),
|
||||
countQuery.execute(),
|
||||
]);
|
||||
const [documents, countResult] = await Promise.all([documentsQuery.execute(), countQuery.execute()]);
|
||||
|
||||
const count = Number((countResult[0] as { count: number })?.count || 0);
|
||||
|
||||
@@ -302,9 +298,7 @@ async function getOrganisationSummary(
|
||||
.where('e.type', '=', sql.lit(EnvelopeType.DOCUMENT))
|
||||
.select([
|
||||
sql<number>`count(e.id)`.as('totalDocuments'),
|
||||
sql<number>`count(case when e.status in ('DRAFT', 'PENDING') then 1 end)`.as(
|
||||
'activeDocuments',
|
||||
),
|
||||
sql<number>`count(case when e.status in ('DRAFT', 'PENDING') then 1 end)`.as('activeDocuments'),
|
||||
sql<number>`count(case when e.status = 'COMPLETED' then 1 end)`.as('completedDocuments'),
|
||||
sql<number>`count(case when e.status = 'COMPLETED' then 1 end)`.as('volumeAllTime'),
|
||||
(createdAtFrom
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||
|
||||
export const getRecipientsStats = async () => {
|
||||
const results = await prisma.recipient.groupBy({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
import type { DateRange } from '@documenso/lib/types/search-params';
|
||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||
import { DocumentStatus, EnvelopeType } from '@prisma/client';
|
||||
|
||||
export type OrganisationInsights = {
|
||||
id: number;
|
||||
@@ -37,10 +36,7 @@ export async function getSigningVolume({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@@ -82,10 +78,7 @@ export async function getSigningVolume({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@@ -153,10 +146,7 @@ export async function getOrganisationInsights({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
@@ -165,9 +155,7 @@ export async function getOrganisationInsights({
|
||||
'o.createdAt as createdAt',
|
||||
'o.customerId as customerId',
|
||||
sql<string>`COALESCE(o.name, 'Unknown')`.as('name'),
|
||||
sql<string>`CASE WHEN s.status IS NOT NULL THEN s.status ELSE NULL END`.as(
|
||||
'subscriptionStatus',
|
||||
),
|
||||
sql<string>`CASE WHEN s.status IS NOT NULL THEN s.status ELSE NULL END`.as('subscriptionStatus'),
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
@@ -212,10 +200,7 @@ export async function getOrganisationInsights({
|
||||
eb.or([
|
||||
eb('o.name', 'ilike', `%${search}%`),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Team as t')
|
||||
.whereRef('t.organisationId', '=', 'o.id')
|
||||
.where('t.name', 'ilike', `%${search}%`),
|
||||
eb.selectFrom('Team as t').whereRef('t.organisationId', '=', 'o.id').where('t.name', 'ilike', `%${search}%`),
|
||||
),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
|
||||
import { SubscriptionStatus, UserSecurityAuditLogType } from '@documenso/prisma/client';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export const getUsersCount = async () => {
|
||||
return await prisma.user.count();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { type RecipientRole, SigningStatus } from '@prisma/client';
|
||||
|
||||
export type UpdateRecipientOptions = {
|
||||
id: number;
|
||||
name: string | undefined;
|
||||
email: string | undefined;
|
||||
role: RecipientRole | undefined;
|
||||
};
|
||||
|
||||
export const updateRecipient = async ({ id, name, email }: UpdateRecipientOptions) => {
|
||||
export const updateRecipient = async ({ id, name, email, role }: UpdateRecipientOptions) => {
|
||||
const recipient = await prisma.recipient.findFirstOrThrow({
|
||||
where: {
|
||||
id,
|
||||
@@ -26,6 +26,7 @@ export const updateRecipient = async ({ id, name, email }: UpdateRecipientOption
|
||||
data: {
|
||||
name,
|
||||
email,
|
||||
role,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Role } from '@prisma/client';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Role } from '@prisma/client';
|
||||
|
||||
export type UpdateUserOptions = {
|
||||
id: number;
|
||||
|
||||
Reference in New Issue
Block a user