mirror of
https://github.com/documenso/documenso.git
synced 2026-07-27 02:15:05 +10:00
fix: invalid folder queries (#1898)
Currently the majority of folder mutations only work if the user is the owner of the folder.
This commit is contained in:
@@ -116,8 +116,8 @@ export const FolderDeleteDialog = ({ folder, isOpen, onOpenChange }: FolderDelet
|
|||||||
<Alert variant="destructive">
|
<Alert variant="destructive">
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<Trans>
|
<Trans>
|
||||||
This folder contains multiple items. Deleting it will also delete all items in the
|
This folder contains multiple items. Deleting it will remove all subfolders and move
|
||||||
folder, including nested folders and their contents.
|
all nested documents and templates to the root folder.
|
||||||
</Trans>
|
</Trans>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
+41
-22
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -14,6 +14,7 @@ import type { TFolderWithSubfolders } from '@documenso/trpc/server/folder-router
|
|||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
@@ -40,7 +41,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
|||||||
|
|
||||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||||
|
|
||||||
export type FolderSettingsDialogProps = {
|
export type FolderUpdateDialogProps = {
|
||||||
folder: TFolderWithSubfolders | null;
|
folder: TFolderWithSubfolders | null;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
@@ -53,12 +54,8 @@ export const ZUpdateFolderFormSchema = z.object({
|
|||||||
|
|
||||||
export type TUpdateFolderFormSchema = z.infer<typeof ZUpdateFolderFormSchema>;
|
export type TUpdateFolderFormSchema = z.infer<typeof ZUpdateFolderFormSchema>;
|
||||||
|
|
||||||
export const FolderSettingsDialog = ({
|
export const FolderUpdateDialog = ({ folder, isOpen, onOpenChange }: FolderUpdateDialogProps) => {
|
||||||
folder,
|
const { t } = useLingui();
|
||||||
isOpen,
|
|
||||||
onOpenChange,
|
|
||||||
}: FolderSettingsDialogProps) => {
|
|
||||||
const { _ } = useLingui();
|
|
||||||
const team = useOptionalCurrentTeam();
|
const team = useOptionalCurrentTeam();
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@@ -84,7 +81,9 @@ export const FolderSettingsDialog = ({
|
|||||||
}, [folder, form]);
|
}, [folder, form]);
|
||||||
|
|
||||||
const onFormSubmit = async (data: TUpdateFolderFormSchema) => {
|
const onFormSubmit = async (data: TUpdateFolderFormSchema) => {
|
||||||
if (!folder) return;
|
if (!folder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateFolder({
|
await updateFolder({
|
||||||
@@ -96,7 +95,7 @@ export const FolderSettingsDialog = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Folder updated successfully`),
|
title: t`Folder updated successfully`,
|
||||||
});
|
});
|
||||||
|
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
@@ -105,7 +104,7 @@ export const FolderSettingsDialog = ({
|
|||||||
|
|
||||||
if (error.code === AppErrorCode.NOT_FOUND) {
|
if (error.code === AppErrorCode.NOT_FOUND) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Folder not found`),
|
title: t`Folder not found`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,8 +114,12 @@ export const FolderSettingsDialog = ({
|
|||||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Folder Settings</DialogTitle>
|
<DialogTitle>
|
||||||
<DialogDescription>Manage the settings for this folder.</DialogDescription>
|
<Trans>Folder Settings</Trans>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<Trans>Manage the settings for this folder.</Trans>
|
||||||
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
@@ -126,7 +129,9 @@ export const FolderSettingsDialog = ({
|
|||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>
|
||||||
|
<Trans>Name</Trans>
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -141,19 +146,25 @@ export const FolderSettingsDialog = ({
|
|||||||
name="visibility"
|
name="visibility"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Visibility</FormLabel>
|
<FormLabel>
|
||||||
|
<Trans>Visibility</Trans>
|
||||||
|
</FormLabel>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select visibility" />
|
<SelectValue placeholder={t`Select visibility`} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value={DocumentVisibility.EVERYONE}>Everyone</SelectItem>
|
<SelectItem value={DocumentVisibility.EVERYONE}>
|
||||||
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE}>
|
<Trans>Everyone</Trans>
|
||||||
Managers and above
|
</SelectItem>
|
||||||
|
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE}>
|
||||||
|
<Trans>Managers and above</Trans>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={DocumentVisibility.ADMIN}>
|
||||||
|
<Trans>Admins only</Trans>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value={DocumentVisibility.ADMIN}>Admins only</SelectItem>
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@@ -163,7 +174,15 @@ export const FolderSettingsDialog = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button type="submit">Save Changes</Button>
|
<DialogClose asChild>
|
||||||
|
<Button variant="secondary">
|
||||||
|
<Trans>Cancel</Trans>
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
|
||||||
|
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||||
|
<Trans>Update</Trans>
|
||||||
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -13,7 +13,7 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton';
|
|||||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||||
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
||||||
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
||||||
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog';
|
||||||
import { TemplateCreateDialog } from '~/components/dialogs/template-create-dialog';
|
import { TemplateCreateDialog } from '~/components/dialogs/template-create-dialog';
|
||||||
import { DocumentUploadDropzone } from '~/components/general/document/document-upload';
|
import { DocumentUploadDropzone } from '~/components/general/document/document-upload';
|
||||||
import { FolderCard, FolderCardEmpty } from '~/components/general/folder/folder-card';
|
import { FolderCard, FolderCardEmpty } from '~/components/general/folder/folder-card';
|
||||||
@@ -219,7 +219,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FolderSettingsDialog
|
<FolderUpdateDialog
|
||||||
folder={folderToSettings}
|
folder={folderToSettings}
|
||||||
isOpen={isSettingsFolderOpen}
|
isOpen={isSettingsFolderOpen}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Input } from '@documenso/ui/primitives/input';
|
|||||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||||
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
||||||
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
||||||
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog';
|
||||||
import { FolderCard } from '~/components/general/folder/folder-card';
|
import { FolderCard } from '~/components/general/folder/folder-card';
|
||||||
import { useCurrentTeam } from '~/providers/team';
|
import { useCurrentTeam } from '~/providers/team';
|
||||||
import { appMetaTags } from '~/utils/meta';
|
import { appMetaTags } from '~/utils/meta';
|
||||||
@@ -177,7 +177,7 @@ export default function DocumentsFoldersPage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FolderSettingsDialog
|
<FolderUpdateDialog
|
||||||
folder={folderToSettings}
|
folder={folderToSettings}
|
||||||
isOpen={isSettingsFolderOpen}
|
isOpen={isSettingsFolderOpen}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Input } from '@documenso/ui/primitives/input';
|
|||||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||||
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog';
|
||||||
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog';
|
||||||
import { FolderSettingsDialog } from '~/components/dialogs/folder-settings-dialog';
|
import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog';
|
||||||
import { FolderCard } from '~/components/general/folder/folder-card';
|
import { FolderCard } from '~/components/general/folder/folder-card';
|
||||||
import { useCurrentTeam } from '~/providers/team';
|
import { useCurrentTeam } from '~/providers/team';
|
||||||
import { appMetaTags } from '~/utils/meta';
|
import { appMetaTags } from '~/utils/meta';
|
||||||
@@ -177,7 +177,7 @@ export default function TemplatesFoldersPage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FolderSettingsDialog
|
<FolderUpdateDialog
|
||||||
folder={folderToSettings}
|
folder={folderToSettings}
|
||||||
isOpen={isSettingsFolderOpen}
|
isOpen={isSettingsFolderOpen}
|
||||||
onOpenChange={(open: boolean) => {
|
onOpenChange={(open: boolean) => {
|
||||||
|
|||||||
@@ -1432,7 +1432,6 @@ const updateDocument = async ({
|
|||||||
return await prisma.document.update({
|
return await prisma.document.update({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
userId,
|
|
||||||
team: buildTeamWhereQuery({ teamId, userId }),
|
team: buildTeamWhereQuery({ teamId, userId }),
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ test('[TEAMS]: can rename a document folder', async ({ page }) => {
|
|||||||
await page.getByRole('menuitem', { name: 'Settings' }).click();
|
await page.getByRole('menuitem', { name: 'Settings' }).click();
|
||||||
|
|
||||||
await page.getByLabel('Name').fill('Team Archive');
|
await page.getByLabel('Name').fill('Team Archive');
|
||||||
await page.getByRole('button', { name: 'Save Changes' }).click();
|
await page.getByRole('button', { name: 'Update' }).click();
|
||||||
|
|
||||||
await expect(page.getByText('Team Archive')).toBeVisible();
|
await expect(page.getByText('Team Archive')).toBeVisible();
|
||||||
});
|
});
|
||||||
@@ -470,7 +470,7 @@ test('[TEAMS]: can rename a template folder', async ({ page }) => {
|
|||||||
await page.getByRole('menuitem', { name: 'Settings' }).click();
|
await page.getByRole('menuitem', { name: 'Settings' }).click();
|
||||||
|
|
||||||
await page.getByLabel('Name').fill('Updated Team Template Folder');
|
await page.getByLabel('Name').fill('Updated Team Template Folder');
|
||||||
await page.getByRole('button', { name: 'Save Changes' }).click();
|
await page.getByRole('button', { name: 'Update' }).click();
|
||||||
|
|
||||||
await expect(page.getByText('Updated Team Template Folder')).toBeVisible();
|
await expect(page.getByText('Updated Team Template Folder')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { prefixedId } from '../../universal/id';
|
|||||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
import { getTeamById } from '../team/get-team';
|
import { getTeamById } from '../team/get-team';
|
||||||
import { getTeamSettings } from '../team/get-team-settings';
|
import { getTeamSettings } from '../team/get-team-settings';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
@@ -58,8 +59,10 @@ export const createDocument = async ({
|
|||||||
const folder = await prisma.folder.findFirst({
|
const folder = await prisma.folder.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: folderId,
|
id: folderId,
|
||||||
userId,
|
team: buildTeamWhereQuery({
|
||||||
teamId,
|
teamId,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
visibility: true,
|
visibility: true,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export const deleteField = async ({
|
|||||||
id: fieldId,
|
id: fieldId,
|
||||||
document: {
|
document: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
userId,
|
|
||||||
team: buildTeamWhereQuery({ teamId, userId }),
|
team: buildTeamWhereQuery({ teamId, userId }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export const updateField = async ({
|
|||||||
id: fieldId,
|
id: fieldId,
|
||||||
document: {
|
document: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
userId,
|
|
||||||
team: buildTeamWhereQuery({ teamId, userId }),
|
team: buildTeamWhereQuery({ teamId, userId }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { match } from 'ts-pattern';
|
|||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
import { getTeamById } from '../team/get-team';
|
import { getTeamById } from '../team/get-team';
|
||||||
|
|
||||||
export interface DeleteFolderOptions {
|
export interface DeleteFolderOptions {
|
||||||
@@ -18,8 +19,10 @@ export const deleteFolder = async ({ userId, teamId, folderId }: DeleteFolderOpt
|
|||||||
const folder = await prisma.folder.findFirst({
|
const folder = await prisma.folder.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: folderId,
|
id: folderId,
|
||||||
userId,
|
team: buildTeamWhereQuery({
|
||||||
teamId,
|
teamId,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
documents: true,
|
documents: true,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|||||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
|
|
||||||
export interface MoveFolderOptions {
|
export interface MoveFolderOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
@@ -15,8 +17,10 @@ export const moveFolder = async ({ userId, teamId, folderId, parentId }: MoveFol
|
|||||||
const folder = await tx.folder.findFirst({
|
const folder = await tx.folder.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: folderId,
|
id: folderId,
|
||||||
userId,
|
team: buildTeamWhereQuery({
|
||||||
teamId,
|
teamId,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|||||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
|
|
||||||
export interface MoveTemplateToFolderOptions {
|
export interface MoveTemplateToFolderOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
@@ -15,45 +17,47 @@ export const moveTemplateToFolder = async ({
|
|||||||
templateId,
|
templateId,
|
||||||
folderId,
|
folderId,
|
||||||
}: MoveTemplateToFolderOptions) => {
|
}: MoveTemplateToFolderOptions) => {
|
||||||
return await prisma.$transaction(async (tx) => {
|
const template = await prisma.template.findFirst({
|
||||||
const template = await tx.template.findFirst({
|
where: {
|
||||||
where: {
|
id: templateId,
|
||||||
id: templateId,
|
team: buildTeamWhereQuery({
|
||||||
userId,
|
|
||||||
teamId,
|
teamId,
|
||||||
},
|
userId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Template not found',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!template) {
|
if (folderId !== null) {
|
||||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
const folder = await prisma.folder.findFirst({
|
||||||
message: 'Template not found',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folderId !== null) {
|
|
||||||
const folder = await tx.folder.findFirst({
|
|
||||||
where: {
|
|
||||||
id: folderId,
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
type: FolderType.TEMPLATE,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!folder) {
|
|
||||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
|
||||||
message: 'Folder not found',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await tx.template.update({
|
|
||||||
where: {
|
where: {
|
||||||
id: templateId,
|
id: folderId,
|
||||||
},
|
team: buildTeamWhereQuery({
|
||||||
data: {
|
teamId,
|
||||||
folderId,
|
userId,
|
||||||
|
}),
|
||||||
|
type: FolderType.TEMPLATE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||||
|
message: 'Folder not found',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await prisma.template.update({
|
||||||
|
where: {
|
||||||
|
id: templateId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
folderId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
import type { TFolderType } from '../../types/folder-type';
|
import type { TFolderType } from '../../types/folder-type';
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
|
|
||||||
export interface PinFolderOptions {
|
export interface PinFolderOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
@@ -14,8 +15,10 @@ export const pinFolder = async ({ userId, teamId, folderId, type }: PinFolderOpt
|
|||||||
const folder = await prisma.folder.findFirst({
|
const folder = await prisma.folder.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: folderId,
|
id: folderId,
|
||||||
userId,
|
team: buildTeamWhereQuery({
|
||||||
teamId,
|
teamId,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
type,
|
type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
import type { TFolderType } from '../../types/folder-type';
|
import type { TFolderType } from '../../types/folder-type';
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
|
|
||||||
export interface UnpinFolderOptions {
|
export interface UnpinFolderOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
@@ -14,8 +15,10 @@ export const unpinFolder = async ({ userId, teamId, folderId, type }: UnpinFolde
|
|||||||
const folder = await prisma.folder.findFirst({
|
const folder = await prisma.folder.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: folderId,
|
id: folderId,
|
||||||
userId,
|
team: buildTeamWhereQuery({
|
||||||
teamId,
|
teamId,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
type,
|
type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { DocumentVisibility } from '@documenso/prisma/generated/types';
|
|||||||
|
|
||||||
import type { TFolderType } from '../../types/folder-type';
|
import type { TFolderType } from '../../types/folder-type';
|
||||||
import { FolderType } from '../../types/folder-type';
|
import { FolderType } from '../../types/folder-type';
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
|
|
||||||
export interface UpdateFolderOptions {
|
export interface UpdateFolderOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
@@ -25,8 +26,10 @@ export const updateFolder = async ({
|
|||||||
const folder = await prisma.folder.findFirst({
|
const folder = await prisma.folder.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: folderId,
|
id: folderId,
|
||||||
userId,
|
team: buildTeamWhereQuery({
|
||||||
teamId,
|
teamId,
|
||||||
|
userId,
|
||||||
|
}),
|
||||||
type,
|
type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
|
|
||||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
|
||||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
|
||||||
|
|
||||||
export type CreateTeamBillingPortalOptions = {
|
|
||||||
userId: number;
|
|
||||||
teamId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createTeamBillingPortal = async ({
|
|
||||||
userId,
|
|
||||||
teamId,
|
|
||||||
}: CreateTeamBillingPortalOptions) => {
|
|
||||||
if (!IS_BILLING_ENABLED()) {
|
|
||||||
throw new Error('Billing is not enabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
const team = await prisma.team.findFirstOrThrow({
|
|
||||||
where: {
|
|
||||||
id: teamId,
|
|
||||||
members: {
|
|
||||||
some: {
|
|
||||||
userId,
|
|
||||||
role: {
|
|
||||||
in: TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_BILLING'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
subscription: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!team.subscription) {
|
|
||||||
throw new Error('Team has no subscription');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!team.customerId) {
|
|
||||||
throw new Error('Team has no customerId');
|
|
||||||
}
|
|
||||||
|
|
||||||
return getPortalSession({
|
|
||||||
customerId: team.customerId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -2,6 +2,8 @@ import type { WebhookTriggerEvents } from '@prisma/client';
|
|||||||
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||||
|
|
||||||
export type GetAllWebhooksByEventTriggerOptions = {
|
export type GetAllWebhooksByEventTriggerOptions = {
|
||||||
event: WebhookTriggerEvents;
|
event: WebhookTriggerEvents;
|
||||||
userId: number;
|
userId: number;
|
||||||
@@ -19,22 +21,10 @@ export const getAllWebhooksByEventTrigger = async ({
|
|||||||
eventTriggers: {
|
eventTriggers: {
|
||||||
has: event,
|
has: event,
|
||||||
},
|
},
|
||||||
team: {
|
team: buildTeamWhereQuery({
|
||||||
id: teamId,
|
teamId,
|
||||||
teamGroups: {
|
userId,
|
||||||
some: {
|
}),
|
||||||
organisationGroup: {
|
|
||||||
organisationGroupMembers: {
|
|
||||||
some: {
|
|
||||||
organisationMember: {
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Document" DROP CONSTRAINT "Document_folderId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Template" DROP CONSTRAINT "Template_folderId_fkey";
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Document" ADD CONSTRAINT "Document_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Template" ADD CONSTRAINT "Template_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -397,7 +397,7 @@ model Document {
|
|||||||
template Template? @relation(fields: [templateId], references: [id], onDelete: SetNull)
|
template Template? @relation(fields: [templateId], references: [id], onDelete: SetNull)
|
||||||
|
|
||||||
auditLogs DocumentAuditLog[]
|
auditLogs DocumentAuditLog[]
|
||||||
folder Folder? @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||||||
folderId String?
|
folderId String?
|
||||||
|
|
||||||
@@unique([documentDataId])
|
@@unique([documentDataId])
|
||||||
@@ -866,7 +866,7 @@ model Template {
|
|||||||
directLink TemplateDirectLink?
|
directLink TemplateDirectLink?
|
||||||
documents Document[]
|
documents Document[]
|
||||||
|
|
||||||
folder Folder? @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
||||||
folderId String?
|
folderId String?
|
||||||
|
|
||||||
@@unique([templateDocumentDataId])
|
@@unique([templateDocumentDataId])
|
||||||
|
|||||||
Reference in New Issue
Block a user