feat: add organisations (#1820)

This commit is contained in:
David Nguyen
2025-06-10 11:49:52 +10:00
committed by GitHub
parent 0b37f19641
commit e6dc237ad2
631 changed files with 37616 additions and 25695 deletions

View File

@ -1,16 +1,14 @@
import { TeamMemberRole } from '@prisma/client';
import type { Team, TeamGlobalSettings } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
import type { TFolderType } from '../../types/folder-type';
import { FolderType } from '../../types/folder-type';
import { determineDocumentVisibility } from '../../utils/document-visibility';
import { getTeamById } from '../team/get-team';
import { getTeamSettings } from '../team/get-team-settings';
export interface CreateFolderOptions {
userId: number;
teamId?: number;
teamId: number;
name: string;
parentId?: string | null;
type?: TFolderType;
@ -23,52 +21,9 @@ export const createFolder = async ({
parentId,
type = FolderType.DOCUMENT,
}: CreateFolderOptions) => {
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
include: {
teamMembers: {
select: {
teamId: true,
},
},
},
});
const team = await getTeamById({ userId, teamId });
if (
teamId !== undefined &&
!user.teamMembers.some((teamMember) => teamMember.teamId === teamId)
) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Team not found',
});
}
let team: (Team & { teamGlobalSettings: TeamGlobalSettings | null }) | null = null;
let userTeamRole: TeamMemberRole | undefined;
if (teamId) {
const teamWithUserRole = await prisma.team.findFirstOrThrow({
where: {
id: teamId,
},
include: {
teamGlobalSettings: true,
members: {
where: {
userId: userId,
},
select: {
role: true,
},
},
},
});
team = teamWithUserRole;
userTeamRole = teamWithUserRole.members[0]?.role;
}
const settings = await getTeamSettings({ userId, teamId });
return await prisma.folder.create({
data: {
@ -77,10 +32,7 @@ export const createFolder = async ({
teamId,
parentId,
type,
visibility: determineDocumentVisibility(
team?.teamGlobalSettings?.documentVisibility,
userTeamRole ?? TeamMemberRole.MEMBER,
),
visibility: determineDocumentVisibility(settings.documentVisibility, team.currentTeamRole),
},
});
};

View File

@ -4,45 +4,16 @@ import { match } from 'ts-pattern';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
import { getTeamById } from '../team/get-team';
export interface DeleteFolderOptions {
userId: number;
teamId?: number;
teamId: number;
folderId: string;
}
export const deleteFolder = async ({ userId, teamId, folderId }: DeleteFolderOptions) => {
let teamMemberRole: TeamMemberRole | null = null;
if (teamId) {
const team = await prisma.team.findFirst({
where: {
id: teamId,
members: {
some: {
userId,
},
},
},
include: {
members: {
where: {
userId,
},
select: {
role: true,
},
},
},
});
if (!team) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Team not found',
});
}
teamMemberRole = team.members[0]?.role ?? null;
}
const team = await getTeamById({ userId, teamId });
const folder = await prisma.folder.findFirst({
where: {
@ -63,18 +34,16 @@ export const deleteFolder = async ({ userId, teamId, folderId }: DeleteFolderOpt
});
}
if (teamId && teamMemberRole) {
const hasPermission = match(teamMemberRole)
.with(TeamMemberRole.ADMIN, () => true)
.with(TeamMemberRole.MANAGER, () => folder.visibility !== DocumentVisibility.ADMIN)
.with(TeamMemberRole.MEMBER, () => folder.visibility === DocumentVisibility.EVERYONE)
.otherwise(() => false);
const hasPermission = match(team.currentTeamRole)
.with(TeamMemberRole.ADMIN, () => true)
.with(TeamMemberRole.MANAGER, () => folder.visibility !== DocumentVisibility.ADMIN)
.with(TeamMemberRole.MEMBER, () => folder.visibility === DocumentVisibility.EVERYONE)
.otherwise(() => false);
if (!hasPermission) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to delete this folder',
});
}
if (!hasPermission) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'You do not have permission to delete this folder',
});
}
return await prisma.folder.delete({

View File

@ -5,49 +5,19 @@ import { prisma } from '@documenso/prisma';
import { DocumentVisibility } from '../../types/document-visibility';
import type { TFolderType } from '../../types/folder-type';
import { getTeamById } from '../team/get-team';
export interface FindFoldersOptions {
userId: number;
teamId?: number;
teamId: number;
parentId?: string | null;
type?: TFolderType;
}
export const findFolders = async ({ userId, teamId, parentId, type }: FindFoldersOptions) => {
let team = null;
let teamMemberRole = null;
const team = await getTeamById({ userId, teamId });
if (teamId !== undefined) {
try {
team = await prisma.team.findFirstOrThrow({
where: {
id: teamId,
members: {
some: {
userId,
},
},
},
include: {
members: {
where: {
userId,
},
select: {
role: true,
},
},
},
});
teamMemberRole = team.members[0].role;
} catch (error) {
console.error('Error finding team:', error);
throw error;
}
}
const visibilityFilters = match(teamMemberRole)
const visibilityFilters = match(team.currentTeamRole)
.with(TeamMemberRole.ADMIN, () => ({
visibility: {
in: [
@ -67,14 +37,12 @@ export const findFolders = async ({ userId, teamId, parentId, type }: FindFolder
const whereClause = {
AND: [
{ parentId },
teamId
? {
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
}
: { userId, teamId: null },
{
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
},
],
};
@ -94,7 +62,8 @@ export const findFolders = async ({ userId, teamId, parentId, type }: FindFolder
prisma.folder.findMany({
where: {
parentId: folder.id,
...(teamId ? { teamId, ...visibilityFilters } : { userId, teamId: null }),
teamId,
...visibilityFilters,
},
orderBy: {
createdAt: 'desc',
@ -113,7 +82,8 @@ export const findFolders = async ({ userId, teamId, parentId, type }: FindFolder
prisma.folder.count({
where: {
parentId: folder.id,
...(teamId ? { teamId, ...visibilityFilters } : { userId, teamId: null }),
teamId,
...visibilityFilters,
},
}),
]);

View File

@ -5,10 +5,11 @@ import { prisma } from '@documenso/prisma';
import { DocumentVisibility } from '../../types/document-visibility';
import type { TFolderType } from '../../types/folder-type';
import { getTeamById } from '../team/get-team';
export interface GetFolderBreadcrumbsOptions {
userId: number;
teamId?: number;
teamId: number;
folderId: string;
type?: TFolderType;
}
@ -19,39 +20,9 @@ export const getFolderBreadcrumbs = async ({
folderId,
type,
}: GetFolderBreadcrumbsOptions) => {
let teamMemberRole = null;
const team = await getTeamById({ userId, teamId });
if (teamId !== undefined) {
try {
const team = await prisma.team.findFirstOrThrow({
where: {
id: teamId,
members: {
some: {
userId,
},
},
},
include: {
members: {
where: {
userId,
},
select: {
role: true,
},
},
},
});
teamMemberRole = team.members[0].role;
} catch (error) {
console.error('Error finding team:', error);
return [];
}
}
const visibilityFilters = match(teamMemberRole)
const visibilityFilters = match(team.currentTeamRole)
.with(TeamMemberRole.ADMIN, () => ({
visibility: {
in: [
@ -71,14 +42,10 @@ export const getFolderBreadcrumbs = async ({
const whereClause = (folderId: string) => ({
id: folderId,
...(type ? { type } : {}),
...(teamId
? {
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
}
: { userId, teamId: null }),
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
});
const breadcrumbs = [];

View File

@ -6,49 +6,19 @@ import { prisma } from '@documenso/prisma';
import { DocumentVisibility } from '../../types/document-visibility';
import type { TFolderType } from '../../types/folder-type';
import { getTeamById } from '../team/get-team';
export interface GetFolderByIdOptions {
userId: number;
teamId?: number;
teamId: number;
folderId?: string;
type?: TFolderType;
}
export const getFolderById = async ({ userId, teamId, folderId, type }: GetFolderByIdOptions) => {
let teamMemberRole = null;
const team = await getTeamById({ userId, teamId });
if (teamId !== undefined) {
try {
const team = await prisma.team.findFirstOrThrow({
where: {
id: teamId,
members: {
some: {
userId,
},
},
},
include: {
members: {
where: {
userId,
},
select: {
role: true,
},
},
},
});
teamMemberRole = team.members[0].role;
} catch (error) {
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Team not found',
});
}
}
const visibilityFilters = match(teamMemberRole)
const visibilityFilters = match(team.currentTeamRole)
.with(TeamMemberRole.ADMIN, () => ({
visibility: {
in: [
@ -68,14 +38,10 @@ export const getFolderById = async ({ userId, teamId, folderId, type }: GetFolde
const whereClause = {
id: folderId,
...(type ? { type } : {}),
...(teamId
? {
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
}
: { userId, teamId: null }),
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
};
const folder = await prisma.folder.findFirst({

View File

@ -7,9 +7,11 @@ import { FolderType } from '@documenso/lib/types/folder-type';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma';
import { getTeamById } from '../team/get-team';
export interface MoveDocumentToFolderOptions {
userId: number;
teamId?: number;
teamId: number;
documentId: number;
folderId?: string | null;
requestMetadata?: ApiRequestMetadata;
@ -21,41 +23,9 @@ export const moveDocumentToFolder = async ({
documentId,
folderId,
}: MoveDocumentToFolderOptions) => {
let teamMemberRole = null;
const team = await getTeamById({ userId, teamId });
if (teamId !== undefined) {
try {
const team = await prisma.team.findFirstOrThrow({
where: {
id: teamId,
members: {
some: {
userId,
},
},
},
include: {
members: {
where: {
userId,
},
select: {
role: true,
},
},
},
});
teamMemberRole = team.members[0].role;
} catch (error) {
console.error('Error finding team:', error);
throw new AppError(AppErrorCode.NOT_FOUND, {
message: 'Team not found',
});
}
}
const visibilityFilters = match(teamMemberRole)
const visibilityFilters = match(team.currentTeamRole)
.with(TeamMemberRole.ADMIN, () => ({
visibility: {
in: [
@ -74,14 +44,10 @@ export const moveDocumentToFolder = async ({
const documentWhereClause = {
id: documentId,
...(teamId
? {
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
}
: { userId, teamId: null }),
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
};
const document = await prisma.document.findFirst({
@ -98,14 +64,10 @@ export const moveDocumentToFolder = async ({
const folderWhereClause = {
id: folderId,
type: FolderType.DOCUMENT,
...(teamId
? {
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
}
: { userId, teamId: null }),
OR: [
{ teamId, ...visibilityFilters },
{ userId, teamId },
],
};
const folder = await prisma.folder.findFirst({