feat: initial tags

This commit is contained in:
Catalin Pit
2026-07-01 10:10:50 +03:00
parent 562d78e2d7
commit d20d9595c9
44 changed files with 1649 additions and 19 deletions
@@ -36,6 +36,8 @@ export type FindDocumentsOptions = {
senderIds?: number[];
query?: string;
folderId?: string;
includeAllFolders?: boolean;
tagIds?: string[];
/**
* When true (default), use a windowed count that caps early for faster pagination.
* When false, use a full COUNT(*) for exact totals — preferred for external API consumers.
@@ -102,6 +104,22 @@ const senderEmailIs = (eb: EnvelopeExpressionBuilder, email: string) =>
.select(sql.lit(1).as('one')),
);
export const applyEnvelopeTagFilter = (qb: EnvelopeQueryBuilder, tagIds?: string[]) => {
if (!tagIds || tagIds.length === 0) {
return qb;
}
return qb.where((eb) =>
eb.exists(
eb
.selectFrom('EnvelopeTag')
.whereRef('EnvelopeTag.envelopeId', '=', 'Envelope.id')
.where('EnvelopeTag.tagId', 'in', tagIds)
.select(sql.lit(1).as('one')),
),
);
};
export const findDocuments = async ({
userId,
teamId,
@@ -115,6 +133,8 @@ export const findDocuments = async ({
senderIds,
query = '',
folderId,
includeAllFolders = false,
tagIds,
useWindowedCount = true,
}: FindDocumentsOptions) => {
const user = await prisma.user.findFirstOrThrow({
@@ -147,8 +167,12 @@ export const findDocuments = async ({
qb = qb.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT));
// Folder filter
qb =
folderId !== undefined ? qb.where('Envelope.folderId', '=', folderId) : qb.where('Envelope.folderId', 'is', null);
if (!includeAllFolders) {
qb =
folderId !== undefined
? qb.where('Envelope.folderId', '=', folderId)
: qb.where('Envelope.folderId', 'is', null);
}
// Period filter
if (period) {
@@ -199,6 +223,9 @@ export const findDocuments = async ({
);
}
// Tag filter (OR semantics — envelope must have at least one of the selected tags)
qb = applyEnvelopeTagFilter(qb, tagIds);
return qb;
};
@@ -520,6 +547,7 @@ export const findDocuments = async ({
envelopeItems: {
select: { id: true, envelopeId: true, title: true, order: true },
},
tags: { include: { tag: true } },
},
});
+22 -4
View File
@@ -1,4 +1,4 @@
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
import { applyEnvelopeTagFilter, type PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
import { kyselyPrisma, prisma, sql } from '@documenso/prisma';
import type { DB } from '@documenso/prisma/generated/types';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
@@ -57,7 +57,9 @@ export type GetStatsInput = {
period?: PeriodSelectorValue;
search?: string;
folderId?: string;
includeAllFolders?: boolean;
senderIds?: number[];
tagIds?: string[];
};
/**
@@ -80,7 +82,16 @@ const cappedCount = async (qb: EnvelopeQueryBuilder): Promise<number> => {
return Math.min(Number(result.total ?? 0), STATS_COUNT_CAP);
};
export const getStats = async ({ userId, teamId, period, search = '', folderId, senderIds }: GetStatsInput) => {
export const getStats = async ({
userId,
teamId,
period,
search = '',
folderId,
includeAllFolders = false,
senderIds,
tagIds,
}: GetStatsInput) => {
const user = await prisma.user.findFirstOrThrow({
where: { id: userId },
select: { id: true, email: true },
@@ -105,8 +116,12 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
qb = qb.where('Envelope.type', '=', sql.lit(EnvelopeType.DOCUMENT));
// Folder filter
qb =
folderId !== undefined ? qb.where('Envelope.folderId', '=', folderId) : qb.where('Envelope.folderId', 'is', null);
if (!includeAllFolders) {
qb =
folderId !== undefined
? qb.where('Envelope.folderId', '=', folderId)
: qb.where('Envelope.folderId', 'is', null);
}
// Period filter
if (period) {
@@ -121,6 +136,9 @@ export const getStats = async ({ userId, teamId, period, search = '', folderId,
qb = qb.where('Envelope.userId', 'in', senderIds);
}
// Tag filter (OR semantics — envelope must have at least one of the selected tags)
qb = applyEnvelopeTagFilter(qb, tagIds);
// Search filter
if (hasSearch) {
qb = qb.where(({ or, eb }) =>