Compare commits
16 Commits
feat/organ
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc329464ec | ||
|
|
574098f103 | ||
|
|
2819251ec4 | ||
|
|
53abb8f00b | ||
|
|
fc70f78e61 | ||
|
|
aa52316ee3 | ||
|
|
ea64ccae29 | ||
|
|
b87154001a | ||
|
|
d4a7eb299e | ||
|
|
2ef619226e | ||
|
|
65c07032de | ||
|
|
b436331d7d | ||
|
|
81ab220f1e | ||
|
|
cc60437dcd | ||
|
|
171b8008f8 | ||
|
|
5c00b82894 |
10
.env.example
10
.env.example
@@ -40,16 +40,6 @@ NEXT_PRIVATE_SIGNING_GCLOUD_HSM_PUBLIC_CRT_FILE_CONTENTS=
|
|||||||
# OPTIONAL: The path to the Google Cloud Credentials file to use for the gcloud-hsm signing transport.
|
# OPTIONAL: The path to the Google Cloud Credentials file to use for the gcloud-hsm signing transport.
|
||||||
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS=
|
NEXT_PRIVATE_SIGNING_GCLOUD_APPLICATION_CREDENTIALS_CONTENTS=
|
||||||
|
|
||||||
# [[SIGNING]]
|
|
||||||
# OPTIONAL: Defines the signing transport to use. Available options: local (default)
|
|
||||||
NEXT_PRIVATE_SIGNING_TRANSPORT="local"
|
|
||||||
# OPTIONAL: Defines the passphrase for the signing certificate.
|
|
||||||
NEXT_PRIVATE_SIGNING_PASSPHRASE=
|
|
||||||
# OPTIONAL: Defines the file contents for the signing certificate as a base64 encoded string.
|
|
||||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_CONTENTS=
|
|
||||||
# OPTIONAL: Defines the file path for the signing certificate. defaults to ./example/cert.p12
|
|
||||||
NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=
|
|
||||||
|
|
||||||
# [[STORAGE]]
|
# [[STORAGE]]
|
||||||
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
|
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
|
||||||
NEXT_PUBLIC_UPLOAD_TRANSPORT="database"
|
NEXT_PUBLIC_UPLOAD_TRANSPORT="database"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"@documenso/trpc": "*",
|
"@documenso/trpc": "*",
|
||||||
"@documenso/ui": "*",
|
"@documenso/ui": "*",
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.0",
|
||||||
|
"@openstatus/react": "^0.0.3",
|
||||||
"contentlayer": "^0.3.4",
|
"contentlayer": "^0.3.4",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"lucide-react": "^0.279.0",
|
"lucide-react": "^0.279.0",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import LogoImage from '@documenso/assets/logo.png';
|
|||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
|
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
|
||||||
|
|
||||||
|
import { StatusWidgetContainer } from './status-widget-container';
|
||||||
|
|
||||||
export type FooterProps = HTMLAttributes<HTMLDivElement>;
|
export type FooterProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
const SOCIAL_LINKS = [
|
const SOCIAL_LINKS = [
|
||||||
@@ -62,6 +64,10 @@ export const Footer = ({ className, ...props }: FooterProps) => {
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<StatusWidgetContainer />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid w-full max-w-sm grid-cols-2 gap-x-4 gap-y-2 md:w-auto md:gap-x-8">
|
<div className="grid w-full max-w-sm grid-cols-2 gap-x-4 gap-y-2 md:w-auto md:gap-x-8">
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// https://github.com/documenso/documenso/pull/1044/files#r1538258462
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
|
import { StatusWidget } from './status-widget';
|
||||||
|
|
||||||
|
export function StatusWidgetContainer() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<StatusWidgetFallback />}>
|
||||||
|
<StatusWidget />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusWidgetFallback() {
|
||||||
|
return (
|
||||||
|
<div className="border-border inline-flex max-w-fit items-center justify-between space-x-2 rounded-md border border-gray-200 px-2 py-2 pr-3 text-sm">
|
||||||
|
<span className="bg-muted h-2 w-36 animate-pulse rounded-md" />
|
||||||
|
<span className="bg-muted relative inline-flex h-2 w-2 rounded-full" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
75
apps/marketing/src/components/(marketing)/status-widget.tsx
Normal file
75
apps/marketing/src/components/(marketing)/status-widget.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { use, useMemo } from 'react';
|
||||||
|
|
||||||
|
import type { Status } from '@openstatus/react';
|
||||||
|
import { getStatus } from '@openstatus/react';
|
||||||
|
|
||||||
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
|
||||||
|
const getStatusLevel = (level: Status) => {
|
||||||
|
return {
|
||||||
|
operational: {
|
||||||
|
label: 'Operational',
|
||||||
|
color: 'bg-green-500',
|
||||||
|
color2: 'bg-green-400',
|
||||||
|
},
|
||||||
|
degraded_performance: {
|
||||||
|
label: 'Degraded Performance',
|
||||||
|
color: 'bg-yellow-500',
|
||||||
|
color2: 'bg-yellow-400',
|
||||||
|
},
|
||||||
|
partial_outage: {
|
||||||
|
label: 'Partial Outage',
|
||||||
|
color: 'bg-yellow-500',
|
||||||
|
color2: 'bg-yellow-400',
|
||||||
|
},
|
||||||
|
major_outage: {
|
||||||
|
label: 'Major Outage',
|
||||||
|
color: 'bg-red-500',
|
||||||
|
color2: 'bg-red-400',
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
label: 'Unknown',
|
||||||
|
color: 'bg-gray-500',
|
||||||
|
color2: 'bg-gray-400',
|
||||||
|
},
|
||||||
|
incident: {
|
||||||
|
label: 'Incident',
|
||||||
|
color: 'bg-yellow-500',
|
||||||
|
color2: 'bg-yellow-400',
|
||||||
|
},
|
||||||
|
under_maintenance: {
|
||||||
|
label: 'Under Maintenance',
|
||||||
|
color: 'bg-gray-500',
|
||||||
|
color2: 'bg-gray-400',
|
||||||
|
},
|
||||||
|
}[level];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function StatusWidget() {
|
||||||
|
const getStatusMemoized = useMemo(async () => getStatus('documenso-status'), []);
|
||||||
|
const { status } = use(getStatusMemoized);
|
||||||
|
const level = getStatusLevel(status);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className="border-border inline-flex max-w-fit items-center justify-between gap-2 space-x-2 rounded-md border border-gray-200 px-3 py-1 text-sm"
|
||||||
|
href="https://status.documenso.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">{level.label}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="relative ml-auto flex h-1.5 w-1.5">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'absolute inline-flex h-full w-full animate-ping rounded-full opacity-75',
|
||||||
|
level.color2,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span className={cn('relative inline-flex h-1.5 w-1.5 rounded-full', level.color)} />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
"next-axiom": "^1.1.1",
|
"next-axiom": "^1.1.1",
|
||||||
"next-plausible": "^3.10.1",
|
"next-plausible": "^3.10.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
"perfect-freehand": "^1.2.0",
|
"perfect-freehand": "^1.2.0",
|
||||||
"posthog-js": "^1.75.3",
|
"posthog-js": "^1.75.3",
|
||||||
"posthog-node": "^3.1.1",
|
"posthog-node": "^3.1.1",
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
"@types/formidable": "^2.0.6",
|
"@types/formidable": "^2.0.6",
|
||||||
"@types/luxon": "^3.3.1",
|
"@types/luxon": "^3.3.1",
|
||||||
"@types/node": "20.1.0",
|
"@types/node": "20.1.0",
|
||||||
|
"@types/papaparse": "^5.3.14",
|
||||||
"@types/react": "18.2.18",
|
"@types/react": "18.2.18",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
import { Mail, PlusCircle, Trash } from 'lucide-react';
|
import { Download, Mail, MailIcon, PlusCircle, Trash, Upload, UsersIcon } from 'lucide-react';
|
||||||
|
import Papa, { type ParseResult } from 'papaparse';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
import { useFieldArray, useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { downloadFile } from '@documenso/lib/client-only/download-file';
|
||||||
import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
import { TEAM_MEMBER_ROLE_HIERARCHY, TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
||||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { ZCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
import { ZCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
|
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -39,6 +42,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@documenso/ui/primitives/select';
|
} from '@documenso/ui/primitives/select';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
export type InviteTeamMembersDialogProps = {
|
export type InviteTeamMembersDialogProps = {
|
||||||
@@ -51,18 +55,45 @@ const ZInviteTeamMembersFormSchema = z
|
|||||||
.object({
|
.object({
|
||||||
invitations: ZCreateTeamMemberInvitesMutationSchema.shape.invitations,
|
invitations: ZCreateTeamMemberInvitesMutationSchema.shape.invitations,
|
||||||
})
|
})
|
||||||
.refine(
|
// Display exactly which rows are duplicates.
|
||||||
(schema) => {
|
.superRefine((items, ctx) => {
|
||||||
const emails = schema.invitations.map((invitation) => invitation.email.toLowerCase());
|
const uniqueEmails = new Map<string, number>();
|
||||||
|
|
||||||
return new Set(emails).size === emails.length;
|
for (const [index, invitation] of items.invitations.entries()) {
|
||||||
},
|
const email = invitation.email.toLowerCase();
|
||||||
// Dirty hack to handle errors when .root is populated for an array type
|
|
||||||
{ message: 'Members must have unique emails', path: ['members__root'] },
|
const firstFoundIndex = uniqueEmails.get(email);
|
||||||
);
|
|
||||||
|
if (firstFoundIndex === undefined) {
|
||||||
|
uniqueEmails.set(email, index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'Emails must be unique',
|
||||||
|
path: ['invitations', index, 'email'],
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'Emails must be unique',
|
||||||
|
path: ['invitations', firstFoundIndex, 'email'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
type TInviteTeamMembersFormSchema = z.infer<typeof ZInviteTeamMembersFormSchema>;
|
type TInviteTeamMembersFormSchema = z.infer<typeof ZInviteTeamMembersFormSchema>;
|
||||||
|
|
||||||
|
type TabTypes = 'INDIVIDUAL' | 'BULK';
|
||||||
|
|
||||||
|
const ZImportTeamMemberSchema = z.array(
|
||||||
|
z.object({
|
||||||
|
email: z.string().email(),
|
||||||
|
role: z.nativeEnum(TeamMemberRole),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export const InviteTeamMembersDialog = ({
|
export const InviteTeamMembersDialog = ({
|
||||||
currentUserTeamRole,
|
currentUserTeamRole,
|
||||||
teamId,
|
teamId,
|
||||||
@@ -70,6 +101,8 @@ export const InviteTeamMembersDialog = ({
|
|||||||
...props
|
...props
|
||||||
}: InviteTeamMembersDialogProps) => {
|
}: InviteTeamMembersDialogProps) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [invitationType, setInvitationType] = useState<TabTypes>('INDIVIDUAL');
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
@@ -130,9 +163,75 @@ export const InviteTeamMembersDialog = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
form.reset();
|
form.reset();
|
||||||
|
setInvitationType('INDIVIDUAL');
|
||||||
}
|
}
|
||||||
}, [open, form]);
|
}, [open, form]);
|
||||||
|
|
||||||
|
const onFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.target.files?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const csvFile = e.target.files[0];
|
||||||
|
|
||||||
|
Papa.parse(csvFile, {
|
||||||
|
skipEmptyLines: true,
|
||||||
|
comments: 'Work email,Job title',
|
||||||
|
complete: (results: ParseResult<string[]>) => {
|
||||||
|
const members = results.data.map((row) => {
|
||||||
|
const [email, role] = row;
|
||||||
|
|
||||||
|
return {
|
||||||
|
email: email.trim(),
|
||||||
|
role: role.trim().toUpperCase(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the first row if it contains the headers.
|
||||||
|
if (members.length > 1 && members[0].role.toUpperCase() === 'ROLE') {
|
||||||
|
members.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const importedInvitations = ZImportTeamMemberSchema.parse(members);
|
||||||
|
|
||||||
|
form.setValue('invitations', importedInvitations);
|
||||||
|
form.clearErrors('invitations');
|
||||||
|
|
||||||
|
setInvitationType('INDIVIDUAL');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err.message);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
|
title: 'Something went wrong',
|
||||||
|
description: 'Please check the CSV file and make sure it is according to our format',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadTemplate = () => {
|
||||||
|
const data = [
|
||||||
|
{ email: 'admin@documenso.com', role: 'Admin' },
|
||||||
|
{ email: 'manager@documenso.com', role: 'Manager' },
|
||||||
|
{ email: 'member@documenso.com', role: 'Member' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const csvContent =
|
||||||
|
'Email address,Role\n' + data.map((row) => `${row.email},${row.role}`).join('\n');
|
||||||
|
|
||||||
|
const blob = new Blob([csvContent], {
|
||||||
|
type: 'text/csv',
|
||||||
|
});
|
||||||
|
|
||||||
|
downloadFile({
|
||||||
|
filename: 'documenso-team-member-invites-template.csv',
|
||||||
|
data: blob,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
{...props}
|
{...props}
|
||||||
@@ -152,12 +251,31 @@ export const InviteTeamMembersDialog = ({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
defaultValue="INDIVIDUAL"
|
||||||
|
value={invitationType}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
onValueChange={(value) => setInvitationType(value as TabTypes)}
|
||||||
|
>
|
||||||
|
<TabsList className="w-full">
|
||||||
|
<TabsTrigger value="INDIVIDUAL" className="hover:text-foreground w-full">
|
||||||
|
<MailIcon size={20} className="mr-2" />
|
||||||
|
Invite Members
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
<TabsTrigger value="BULK" className="hover:text-foreground w-full">
|
||||||
|
<UsersIcon size={20} className="mr-2" /> Bulk Import
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="INDIVIDUAL">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
<form onSubmit={form.handleSubmit(onFormSubmit)}>
|
||||||
<fieldset
|
<fieldset
|
||||||
className="flex h-full flex-col space-y-4"
|
className="flex h-full flex-col space-y-4"
|
||||||
disabled={form.formState.isSubmitting}
|
disabled={form.formState.isSubmitting}
|
||||||
>
|
>
|
||||||
|
<div className="custom-scrollbar -m-1 max-h-[60vh] space-y-4 overflow-y-auto p-1">
|
||||||
{teamMemberInvites.map((teamMemberInvite, index) => (
|
{teamMemberInvites.map((teamMemberInvite, index) => (
|
||||||
<div className="flex w-full flex-row space-x-4" key={teamMemberInvite.id}>
|
<div className="flex w-full flex-row space-x-4" key={teamMemberInvite.id}>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -213,6 +331,7 @@ export const InviteTeamMembersDialog = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -238,6 +357,38 @@ export const InviteTeamMembersDialog = ({
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="BULK">
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<Card gradient className="h-32">
|
||||||
|
<CardContent
|
||||||
|
className="text-muted-foreground/80 hover:text-muted-foreground/90 flex h-full cursor-pointer flex-col items-center justify-center rounded-lg p-0 transition-colors"
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
|
<Upload className="h-5 w-5" />
|
||||||
|
|
||||||
|
<p className="mt-1 text-sm">Click here to upload</p>
|
||||||
|
|
||||||
|
<input
|
||||||
|
onChange={onFileInputChange}
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
accept=".csv"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="button" variant="secondary" onClick={downloadTemplate}>
|
||||||
|
<Download className="mr-2 h-4 w-4" />
|
||||||
|
Template
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 54320:5432
|
- 54320:5432
|
||||||
|
|
||||||
|
queue:
|
||||||
|
image: postgres:15
|
||||||
|
container_name: queue
|
||||||
|
user: postgres
|
||||||
|
command: -c jit=off
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
POSTGRES_DB: queue
|
||||||
|
ports:
|
||||||
|
- 54321:5432
|
||||||
|
|
||||||
inbucket:
|
inbucket:
|
||||||
image: inbucket/inbucket
|
image: inbucket/inbucket
|
||||||
container_name: mailserver
|
container_name: mailserver
|
||||||
|
|||||||
236
package-lock.json
generated
236
package-lock.json
generated
@@ -42,6 +42,7 @@
|
|||||||
"@documenso/trpc": "*",
|
"@documenso/trpc": "*",
|
||||||
"@documenso/ui": "*",
|
"@documenso/ui": "*",
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.0",
|
||||||
|
"@openstatus/react": "^0.0.3",
|
||||||
"contentlayer": "^0.3.4",
|
"contentlayer": "^0.3.4",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"lucide-react": "^0.279.0",
|
"lucide-react": "^0.279.0",
|
||||||
@@ -115,6 +116,7 @@
|
|||||||
"next-axiom": "^1.1.1",
|
"next-axiom": "^1.1.1",
|
||||||
"next-plausible": "^3.10.1",
|
"next-plausible": "^3.10.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
|
"papaparse": "^5.4.1",
|
||||||
"perfect-freehand": "^1.2.0",
|
"perfect-freehand": "^1.2.0",
|
||||||
"posthog-js": "^1.75.3",
|
"posthog-js": "^1.75.3",
|
||||||
"posthog-node": "^3.1.1",
|
"posthog-node": "^3.1.1",
|
||||||
@@ -138,6 +140,7 @@
|
|||||||
"@types/formidable": "^2.0.6",
|
"@types/formidable": "^2.0.6",
|
||||||
"@types/luxon": "^3.3.1",
|
"@types/luxon": "^3.3.1",
|
||||||
"@types/node": "20.1.0",
|
"@types/node": "20.1.0",
|
||||||
|
"@types/papaparse": "^5.3.14",
|
||||||
"@types/react": "18.2.18",
|
"@types/react": "18.2.18",
|
||||||
"@types/react-dom": "18.2.7",
|
"@types/react-dom": "18.2.7",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
@@ -4140,6 +4143,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
|
||||||
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="
|
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@openstatus/react": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openstatus/react/-/react-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-uDiegz7e3H67pG8lTT+op+6w5keTT7XpcENrREaqlWl5j53TYyO8nheOG1PeNw2/Qgd5KaGeRJJFn1crhTUSYw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@opentelemetry/api": {
|
"node_modules/@opentelemetry/api": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz",
|
||||||
@@ -8081,6 +8092,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
|
||||||
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="
|
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/papaparse": {
|
||||||
|
"version": "5.3.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.14.tgz",
|
||||||
|
"integrity": "sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/parse5": {
|
"node_modules/@types/parse5": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
|
||||||
@@ -8423,6 +8443,18 @@
|
|||||||
"node": ">= 6.0.0"
|
"node": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/aggregate-error": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
|
||||||
|
"dependencies": {
|
||||||
|
"clean-stack": "^2.0.0",
|
||||||
|
"indent-string": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "8.12.0",
|
"version": "8.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||||
@@ -9397,6 +9429,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
||||||
},
|
},
|
||||||
|
"node_modules/clean-stack": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cli-cursor": {
|
"node_modules/cli-cursor": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
|
||||||
@@ -10172,6 +10212,17 @@
|
|||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cron-parser": {
|
||||||
|
"version": "4.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
|
||||||
|
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"luxon": "^3.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-fetch": {
|
"node_modules/cross-fetch": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||||
@@ -10538,6 +10589,17 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delay": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@@ -13652,7 +13714,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -17230,6 +17291,20 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-map": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"aggregate-error": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-try": {
|
"node_modules/p-try": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
@@ -17254,6 +17329,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/papaparse": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw=="
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -17534,6 +17614,124 @@
|
|||||||
"is-reference": "^3.0.0"
|
"is-reference": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz",
|
||||||
|
"integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-connection-string": "^2.6.4",
|
||||||
|
"pg-pool": "^3.6.2",
|
||||||
|
"pg-protocol": "^1.6.1",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-boss": {
|
||||||
|
"version": "9.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-boss/-/pg-boss-9.0.3.tgz",
|
||||||
|
"integrity": "sha512-cUWUiv3sr563yNy0nCZ25Tv5U0m59Y9MhX/flm0vTR012yeVCrqpfboaZP4xFOQPdWipMJpuu4g94HR0SncTgw==",
|
||||||
|
"dependencies": {
|
||||||
|
"cron-parser": "^4.0.0",
|
||||||
|
"delay": "^5.0.0",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"p-map": "^4.0.0",
|
||||||
|
"pg": "^8.5.1",
|
||||||
|
"serialize-error": "^8.1.0",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-boss/node_modules/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz",
|
||||||
|
"integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass/node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
@@ -17792,6 +17990,41 @@
|
|||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/posthog-js": {
|
"node_modules/posthog-js": {
|
||||||
"version": "1.93.2",
|
"version": "1.93.2",
|
||||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.93.2.tgz",
|
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.93.2.tgz",
|
||||||
@@ -24901,6 +25134,7 @@
|
|||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"oslo": "^0.17.0",
|
"oslo": "^0.17.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
|
"pg-boss": "^9.0.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^1.27.1",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { SendMailOptions } from 'nodemailer';
|
||||||
import { createTransport } from 'nodemailer';
|
import { createTransport } from 'nodemailer';
|
||||||
|
|
||||||
import { ResendTransport } from '@documenso/nodemailer-resend';
|
import { ResendTransport } from '@documenso/nodemailer-resend';
|
||||||
@@ -54,3 +55,4 @@ const getTransport = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const mailer = getTransport();
|
export const mailer = getTransport();
|
||||||
|
export type MailOptions = SendMailOptions;
|
||||||
|
|||||||
@@ -27,18 +27,19 @@
|
|||||||
"@next-auth/prisma-adapter": "1.0.7",
|
"@next-auth/prisma-adapter": "1.0.7",
|
||||||
"@noble/ciphers": "0.4.0",
|
"@noble/ciphers": "0.4.0",
|
||||||
"@noble/hashes": "1.3.2",
|
"@noble/hashes": "1.3.2",
|
||||||
|
"@node-rs/bcrypt": "^1.10.0",
|
||||||
"@pdf-lib/fontkit": "^1.1.1",
|
"@pdf-lib/fontkit": "^1.1.1",
|
||||||
"@scure/base": "^1.1.3",
|
"@scure/base": "^1.1.3",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
"@upstash/redis": "^1.20.6",
|
"@upstash/redis": "^1.20.6",
|
||||||
"@vvo/tzdb": "^6.117.0",
|
"@vvo/tzdb": "^6.117.0",
|
||||||
"@node-rs/bcrypt": "^1.10.0",
|
|
||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"oslo": "^0.17.0",
|
"oslo": "^0.17.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
|
"pg-boss": "^9.0.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^1.27.1",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import {
|
import { diffDocumentMetaChanges } from '@documenso/lib/utils/document-audit-logs';
|
||||||
createDocumentAuditLogData,
|
|
||||||
diffDocumentMetaChanges,
|
|
||||||
} from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type CreateDocumentMetaOptions = {
|
export type CreateDocumentMetaOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
@@ -65,8 +64,7 @@ export const upsertDocumentMeta = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
const upsertedDocumentMeta = await prisma.documentMeta.upsert({
|
||||||
const upsertedDocumentMeta = await tx.documentMeta.upsert({
|
|
||||||
where: {
|
where: {
|
||||||
documentId,
|
documentId,
|
||||||
},
|
},
|
||||||
@@ -92,8 +90,9 @@ export const upsertDocumentMeta = async ({
|
|||||||
const changes = diffDocumentMetaChanges(originalDocumentMeta ?? {}, upsertedDocumentMeta);
|
const changes = diffDocumentMetaChanges(originalDocumentMeta ?? {}, upsertedDocumentMeta);
|
||||||
|
|
||||||
if (changes.length > 0) {
|
if (changes.length > 0) {
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_META_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_META_UPDATED,
|
||||||
documentId,
|
documentId,
|
||||||
user,
|
user,
|
||||||
@@ -101,10 +100,9 @@ export const upsertDocumentMeta = async ({
|
|||||||
data: {
|
data: {
|
||||||
changes: diffDocumentMetaChanges(originalDocumentMeta ?? {}, upsertedDocumentMeta),
|
changes: diffDocumentMetaChanges(originalDocumentMeta ?? {}, upsertedDocumentMeta),
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return upsertedDocumentMeta;
|
return upsertedDocumentMeta;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,14 @@
|
|||||||
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import type { TRecipientActionAuth } from '../../types/document-auth';
|
import type { TRecipientActionAuth } from '../../types/document-auth';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
import { sealDocument } from './seal-document';
|
import { sealDocument } from './seal-document';
|
||||||
import { sendPendingEmail } from './send-pending-email';
|
|
||||||
|
|
||||||
export type CompleteDocumentWithTokenOptions = {
|
export type CompleteDocumentWithTokenOptions = {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -93,8 +92,7 @@ export const completeDocumentWithToken = async ({
|
|||||||
// throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
// throw new AppError(AppErrorCode.UNAUTHORIZED, 'Invalid authentication values');
|
||||||
// }
|
// }
|
||||||
|
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.recipient.update({
|
||||||
await tx.recipient.update({
|
|
||||||
where: {
|
where: {
|
||||||
id: recipient.id,
|
id: recipient.id,
|
||||||
},
|
},
|
||||||
@@ -104,8 +102,9 @@ export const completeDocumentWithToken = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user: {
|
user: {
|
||||||
@@ -120,8 +119,7 @@ export const completeDocumentWithToken = async ({
|
|||||||
recipientRole: recipient.role,
|
recipientRole: recipient.role,
|
||||||
// actionAuth: derivedRecipientActionAuth || undefined,
|
// actionAuth: derivedRecipientActionAuth || undefined,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const pendingRecipients = await prisma.recipient.count({
|
const pendingRecipients = await prisma.recipient.count({
|
||||||
@@ -134,7 +132,13 @@ export const completeDocumentWithToken = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (pendingRecipients > 0) {
|
if (pendingRecipients > 0) {
|
||||||
await sendPendingEmail({ documentId, recipientId: recipient.id });
|
await queueJob({
|
||||||
|
job: 'send-pending-email',
|
||||||
|
args: {
|
||||||
|
documentId: document.id,
|
||||||
|
recipientId: recipient.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const documents = await prisma.document.updateMany({
|
const documents = await prisma.document.updateMany({
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
|
|
||||||
export type CreateDocumentOptions = {
|
export type CreateDocumentOptions = {
|
||||||
@@ -44,8 +44,7 @@ export const createDocument = async ({
|
|||||||
throw new AppError(AppErrorCode.NOT_FOUND, 'Team not found');
|
throw new AppError(AppErrorCode.NOT_FOUND, 'Team not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
const document = await prisma.document.create({
|
||||||
const document = await tx.document.create({
|
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
documentDataId,
|
documentDataId,
|
||||||
@@ -54,8 +53,9 @@ export const createDocument = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_CREATED,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user,
|
user,
|
||||||
@@ -63,7 +63,7 @@ export const createDocument = async ({
|
|||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await triggerWebhook({
|
await triggerWebhook({
|
||||||
@@ -74,5 +74,4 @@ export const createDocument = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
@@ -12,7 +11,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
|||||||
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type DeleteDocumentOptions = {
|
export type DeleteDocumentOptions = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -61,11 +60,11 @@ export const deleteDocument = async ({
|
|||||||
|
|
||||||
// if the document is a draft, hard-delete
|
// if the document is a draft, hard-delete
|
||||||
if (status === DocumentStatus.DRAFT) {
|
if (status === DocumentStatus.DRAFT) {
|
||||||
return await prisma.$transaction(async (tx) => {
|
|
||||||
// Currently redundant since deleting a document will delete the audit logs.
|
// Currently redundant since deleting a document will delete the audit logs.
|
||||||
// However may be useful if we disassociate audit lgos and documents if required.
|
// However may be useful if we disassociate audit lgos and documents if required.
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
documentId: id,
|
documentId: id,
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
||||||
user,
|
user,
|
||||||
@@ -73,11 +72,10 @@ export const deleteDocument = async ({
|
|||||||
data: {
|
data: {
|
||||||
type: 'HARD',
|
type: 'HARD',
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return await tx.document.delete({ where: { id, status: DocumentStatus.DRAFT } });
|
return await prisma.document.delete({ where: { id, status: DocumentStatus.DRAFT } });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the document is pending, send cancellation emails to all recipients
|
// if the document is pending, send cancellation emails to all recipients
|
||||||
@@ -93,7 +91,9 @@ export const deleteDocument = async ({
|
|||||||
assetBaseUrl,
|
assetBaseUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mailer.sendMail({
|
await queueJob({
|
||||||
|
job: 'send-mail',
|
||||||
|
args: {
|
||||||
to: {
|
to: {
|
||||||
address: recipient.email,
|
address: recipient.email,
|
||||||
name: recipient.name,
|
name: recipient.name,
|
||||||
@@ -105,15 +105,16 @@ export const deleteDocument = async ({
|
|||||||
subject: 'Document Cancelled',
|
subject: 'Document Cancelled',
|
||||||
html: render(template),
|
html: render(template),
|
||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the document is not a draft, only soft-delete.
|
// If the document is not a draft, only soft-delete.
|
||||||
return await prisma.$transaction(async (tx) => {
|
await queueJob({
|
||||||
await tx.documentAuditLog.create({
|
job: 'create-document-audit-log',
|
||||||
data: createDocumentAuditLogData({
|
args: {
|
||||||
documentId: id,
|
documentId: id,
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
||||||
user,
|
user,
|
||||||
@@ -121,10 +122,10 @@ export const deleteDocument = async ({
|
|||||||
data: {
|
data: {
|
||||||
type: 'SOFT',
|
type: 'SOFT',
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return await tx.document.update({
|
return await prisma.document.update({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
@@ -132,5 +133,4 @@ export const deleteDocument = async ({
|
|||||||
deletedAt: new Date().toISOString(),
|
deletedAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||||
@@ -10,13 +9,13 @@ import {
|
|||||||
} from '@documenso/lib/constants/recipient-roles';
|
} from '@documenso/lib/constants/recipient-roles';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
||||||
import type { Prisma } from '@documenso/prisma/client';
|
import type { Prisma } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
import { getDocumentWhereInput } from './get-document-by-id';
|
import { getDocumentWhereInput } from './get-document-by-id';
|
||||||
|
|
||||||
export type ResendDocumentOptions = {
|
export type ResendDocumentOptions = {
|
||||||
@@ -110,9 +109,9 @@ export const resendDocument = async ({
|
|||||||
|
|
||||||
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
||||||
|
|
||||||
await prisma.$transaction(
|
await queueJob({
|
||||||
async (tx) => {
|
job: 'send-mail',
|
||||||
await mailer.sendMail({
|
args: {
|
||||||
to: {
|
to: {
|
||||||
address: email,
|
address: email,
|
||||||
name,
|
name,
|
||||||
@@ -126,10 +125,12 @@ export const resendDocument = async ({
|
|||||||
: `Please ${actionVerb.toLowerCase()} this document`,
|
: `Please ${actionVerb.toLowerCase()} this document`,
|
||||||
html: render(template),
|
html: render(template),
|
||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user,
|
user,
|
||||||
@@ -142,11 +143,8 @@ export const resendDocument = async ({
|
|||||||
recipientId: recipient.id,
|
recipientId: recipient.id,
|
||||||
isResending: true,
|
isResending: true,
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
{ timeout: 30_000 },
|
});
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,21 +5,20 @@ import path from 'node:path';
|
|||||||
import { PDFDocument } from 'pdf-lib';
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
||||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||||
import { signPdf } from '@documenso/signing';
|
import { signPdf } from '@documenso/signing';
|
||||||
|
|
||||||
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFile } from '../../universal/upload/get-file';
|
||||||
import { putFile } from '../../universal/upload/put-file';
|
import { putFile } from '../../universal/upload/put-file';
|
||||||
import { flattenAnnotations } from '../pdf/flatten-annotations';
|
import { flattenAnnotations } from '../pdf/flatten-annotations';
|
||||||
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
|
import { insertFieldInPDF } from '../pdf/insert-field-in-pdf';
|
||||||
import { normalizeSignatureAppearances } from '../pdf/normalize-signature-appearances';
|
import { normalizeSignatureAppearances } from '../pdf/normalize-signature-appearances';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
import { sendCompletedEmail } from './send-completed-email';
|
|
||||||
|
|
||||||
export type SealDocumentOptions = {
|
export type SealDocumentOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
@@ -126,8 +125,7 @@ export const sealDocument = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.documentData.update({
|
||||||
await tx.documentData.update({
|
|
||||||
where: {
|
where: {
|
||||||
id: documentData.id,
|
id: documentData.id,
|
||||||
},
|
},
|
||||||
@@ -136,8 +134,9 @@ export const sealDocument = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
@@ -145,12 +144,14 @@ export const sealDocument = async ({
|
|||||||
data: {
|
data: {
|
||||||
transactionId: nanoid(),
|
transactionId: nanoid(),
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sendEmail && !isResealing) {
|
if (sendEmail && !isResealing) {
|
||||||
await sendCompletedEmail({ documentId, requestMetadata });
|
await queueJob({
|
||||||
|
job: 'send-completed-email',
|
||||||
|
args: { documentId, requestMetadata },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await triggerWebhook({
|
await triggerWebhook({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
|||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFile } from '../../universal/upload/get-file';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export interface SendDocumentOptions {
|
export interface SendDocumentOptions {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
@@ -86,8 +86,9 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user: null,
|
user: null,
|
||||||
@@ -100,7 +101,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
recipientRole: 'OWNER',
|
recipientRole: 'OWNER',
|
||||||
isResending: false,
|
isResending: false,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,8 +137,9 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user: null,
|
user: null,
|
||||||
@@ -150,7 +152,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
recipientRole: recipient.role,
|
recipientRole: recipient.role,
|
||||||
isResending: false,
|
isResending: false,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document
|
|||||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
||||||
@@ -17,6 +16,7 @@ import {
|
|||||||
RECIPIENT_ROLES_DESCRIPTION,
|
RECIPIENT_ROLES_DESCRIPTION,
|
||||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||||
} from '../../constants/recipient-roles';
|
} from '../../constants/recipient-roles';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
|
|
||||||
export type SendDocumentOptions = {
|
export type SendDocumentOptions = {
|
||||||
@@ -113,8 +113,7 @@ export const sendDocument = async ({
|
|||||||
|
|
||||||
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
const { actionVerb } = RECIPIENT_ROLES_DESCRIPTION[recipient.role];
|
||||||
|
|
||||||
await prisma.$transaction(
|
// TODO: Move this to a seperate queue of it's own
|
||||||
async (tx) => {
|
|
||||||
await mailer.sendMail({
|
await mailer.sendMail({
|
||||||
to: {
|
to: {
|
||||||
address: email,
|
address: email,
|
||||||
@@ -131,7 +130,7 @@ export const sendDocument = async ({
|
|||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.recipient.update({
|
await prisma.recipient.update({
|
||||||
where: {
|
where: {
|
||||||
id: recipient.id,
|
id: recipient.id,
|
||||||
},
|
},
|
||||||
@@ -140,8 +139,9 @@ export const sendDocument = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user,
|
user,
|
||||||
@@ -154,28 +154,25 @@ export const sendDocument = async ({
|
|||||||
recipientId: recipient.id,
|
recipientId: recipient.id,
|
||||||
isResending: false,
|
isResending: false,
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
{ timeout: 30_000 },
|
});
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedDocument = await prisma.$transaction(async (tx) => {
|
|
||||||
if (document.status === DocumentStatus.DRAFT) {
|
if (document.status === DocumentStatus.DRAFT) {
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_SENT,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
user,
|
user,
|
||||||
data: {},
|
data: {},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return await tx.document.update({
|
const updatedDocument = await prisma.document.update({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
},
|
},
|
||||||
@@ -186,7 +183,6 @@ export const sendDocument = async ({
|
|||||||
Recipient: true,
|
Recipient: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
await triggerWebhook({
|
await triggerWebhook({
|
||||||
event: WebhookTriggerEvents.DOCUMENT_SENT,
|
event: WebhookTriggerEvents.DOCUMENT_SENT,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
@@ -12,7 +11,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
|||||||
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type SuperDeleteDocumentOptions = {
|
export type SuperDeleteDocumentOptions = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -49,7 +48,9 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
|||||||
assetBaseUrl,
|
assetBaseUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mailer.sendMail({
|
await queueJob({
|
||||||
|
job: 'send-mail',
|
||||||
|
args: {
|
||||||
to: {
|
to: {
|
||||||
address: recipient.email,
|
address: recipient.email,
|
||||||
name: recipient.name,
|
name: recipient.name,
|
||||||
@@ -61,15 +62,15 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
|||||||
subject: 'Document Cancelled',
|
subject: 'Document Cancelled',
|
||||||
html: render(template),
|
html: render(template),
|
||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// always hard delete if deleted from admin
|
await queueJob({
|
||||||
return await prisma.$transaction(async (tx) => {
|
job: 'create-document-audit-log',
|
||||||
await tx.documentAuditLog.create({
|
args: {
|
||||||
data: createDocumentAuditLogData({
|
|
||||||
documentId: id,
|
documentId: id,
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_DELETED,
|
||||||
user,
|
user,
|
||||||
@@ -77,9 +78,9 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
|||||||
data: {
|
data: {
|
||||||
type: 'HARD',
|
type: 'HARD',
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return await tx.document.delete({ where: { id } });
|
// always hard delete if deleted from admin
|
||||||
});
|
return await prisma.document.delete({ where: { id } });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type UpdateTitleOptions = {
|
export type UpdateTitleOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
@@ -51,11 +52,10 @@ export const updateTitle = async ({
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.$transaction(async (tx) => {
|
|
||||||
// Instead of doing everything in a transaction we can use our knowledge
|
// Instead of doing everything in a transaction we can use our knowledge
|
||||||
// of the current document title to ensure we aren't performing a conflicting
|
// of the current document title to ensure we aren't performing a conflicting
|
||||||
// update.
|
// update.
|
||||||
const updatedDocument = await tx.document.update({
|
const updatedDocument = await prisma.document.update({
|
||||||
where: {
|
where: {
|
||||||
id: documentId,
|
id: documentId,
|
||||||
title: document.title,
|
title: document.title,
|
||||||
@@ -65,8 +65,9 @@ export const updateTitle = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_TITLE_UPDATED,
|
||||||
documentId,
|
documentId,
|
||||||
user,
|
user,
|
||||||
@@ -75,9 +76,8 @@ export const updateTitle = async ({
|
|||||||
from: document.title,
|
from: document.title,
|
||||||
to: updatedDocument.title,
|
to: updatedDocument.title,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return updatedDocument;
|
return updatedDocument;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { ReadStatus } from '@documenso/prisma/client';
|
import { ReadStatus } from '@documenso/prisma/client';
|
||||||
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
import { WebhookTriggerEvents } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import type { TDocumentAccessAuthTypes } from '../../types/document-auth';
|
import type { TDocumentAccessAuthTypes } from '../../types/document-auth';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
import { getDocumentAndRecipientByToken } from './get-document-by-token';
|
import { getDocumentAndRecipientByToken } from './get-document-by-token';
|
||||||
|
|
||||||
@@ -33,8 +33,7 @@ export const viewedDocument = async ({
|
|||||||
|
|
||||||
const { documentId } = recipient;
|
const { documentId } = recipient;
|
||||||
|
|
||||||
await prisma.$transaction(async (tx) => {
|
await prisma.recipient.update({
|
||||||
await tx.recipient.update({
|
|
||||||
where: {
|
where: {
|
||||||
id: recipient.id,
|
id: recipient.id,
|
||||||
},
|
},
|
||||||
@@ -43,8 +42,9 @@ export const viewedDocument = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED,
|
||||||
documentId,
|
documentId,
|
||||||
user: {
|
user: {
|
||||||
@@ -59,8 +59,7 @@ export const viewedDocument = async ({
|
|||||||
recipientRole: recipient.role,
|
recipientRole: recipient.role,
|
||||||
accessAuth: recipientAccessAuth || undefined,
|
accessAuth: recipientAccessAuth || undefined,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const document = await getDocumentAndRecipientByToken({ token, requireAccessAuth: false });
|
const document = await getDocumentAndRecipientByToken({ token, requireAccessAuth: false });
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
|
|||||||
import type { FieldType, Team } from '@documenso/prisma/client';
|
import type { FieldType, Team } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type CreateFieldOptions = {
|
export type CreateFieldOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
@@ -103,8 +103,9 @@ export const createField = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: 'FIELD_CREATED',
|
type: 'FIELD_CREATED',
|
||||||
documentId,
|
documentId,
|
||||||
user: {
|
user: {
|
||||||
@@ -119,7 +120,7 @@ export const createField = async ({
|
|||||||
fieldType: field.type,
|
fieldType: field.type,
|
||||||
},
|
},
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return field;
|
return field;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { prisma } from '@documenso/prisma';
|
|||||||
import type { Team } from '@documenso/prisma/client';
|
import type { Team } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type DeleteFieldOptions = {
|
export type DeleteFieldOptions = {
|
||||||
fieldId: number;
|
fieldId: number;
|
||||||
@@ -67,8 +67,9 @@ export const deleteField = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: 'FIELD_DELETED',
|
type: 'FIELD_DELETED',
|
||||||
documentId,
|
documentId,
|
||||||
user: {
|
user: {
|
||||||
@@ -83,7 +84,7 @@ export const deleteField = async ({
|
|||||||
fieldType: field.type,
|
fieldType: field.type,
|
||||||
},
|
},
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return field;
|
return field;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type RemovedSignedFieldWithTokenOptions = {
|
export type RemovedSignedFieldWithTokenOptions = {
|
||||||
token: string;
|
token: string;
|
||||||
fieldId: number;
|
fieldId: number;
|
||||||
@@ -65,9 +66,11 @@ export const removeSignedFieldWithToken = async ({
|
|||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_UNINSERTED,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user: {
|
user: {
|
||||||
@@ -79,7 +82,6 @@ export const removeSignedFieldWithToken = async ({
|
|||||||
field: field.type,
|
field: field.type,
|
||||||
fieldId: field.secondaryId,
|
fieldId: field.secondaryId,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { prisma } from '@documenso/prisma';
|
|||||||
import type { Field, FieldType } from '@documenso/prisma/client';
|
import type { Field, FieldType } from '@documenso/prisma/client';
|
||||||
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export interface SetFieldsForDocumentOptions {
|
export interface SetFieldsForDocumentOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
@@ -155,8 +157,9 @@ export const setFieldsForDocument = async ({
|
|||||||
|
|
||||||
// Handle field updated audit log.
|
// Handle field updated audit log.
|
||||||
if (field._persisted && changes.length > 0) {
|
if (field._persisted && changes.length > 0) {
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
user,
|
||||||
@@ -165,14 +168,15 @@ export const setFieldsForDocument = async ({
|
|||||||
changes,
|
changes,
|
||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle field created audit log.
|
// Handle field created audit log.
|
||||||
if (!field._persisted) {
|
if (!field._persisted) {
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_CREATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
user,
|
||||||
@@ -180,7 +184,7 @@ export const setFieldsForDocument = async ({
|
|||||||
data: {
|
data: {
|
||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import { AppError, AppErrorCode } from '../../errors/app-error';
|
|||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import type { TRecipientActionAuth } from '../../types/document-auth';
|
import type { TRecipientActionAuth } from '../../types/document-auth';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
|
||||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||||
import { isRecipientAuthorized } from '../document/is-recipient-authorized';
|
import { isRecipientAuthorized } from '../document/is-recipient-authorized';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type SignFieldWithTokenOptions = {
|
export type SignFieldWithTokenOptions = {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -168,8 +168,9 @@ export const signFieldWithToken = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_FIELD_INSERTED,
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
user: {
|
user: {
|
||||||
@@ -199,7 +200,7 @@ export const signFieldWithToken = async ({
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return updatedField;
|
return updatedField;
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import type { FieldType, Team } from '@documenso/prisma/client';
|
|||||||
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData, diffFieldChanges } from '../../utils/document-audit-logs';
|
import { diffFieldChanges } from '../../utils/document-audit-logs';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type UpdateFieldOptions = {
|
export type UpdateFieldOptions = {
|
||||||
fieldId: number;
|
fieldId: number;
|
||||||
@@ -77,8 +78,9 @@ export const updateField = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.FIELD_UPDATED,
|
||||||
documentId,
|
documentId,
|
||||||
user: {
|
user: {
|
||||||
@@ -94,7 +96,7 @@ export const updateField = async ({
|
|||||||
changes: diffFieldChanges(oldField, updatedField),
|
changes: diffFieldChanges(oldField, updatedField),
|
||||||
},
|
},
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return updatedField;
|
return updatedField;
|
||||||
|
|||||||
52
packages/lib/server-only/queue/index.ts
Normal file
52
packages/lib/server-only/queue/index.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import type { WorkHandler } from 'pg-boss';
|
||||||
|
import PgBoss from 'pg-boss';
|
||||||
|
|
||||||
|
import { jobHandlers } from './job';
|
||||||
|
|
||||||
|
type QueueState = {
|
||||||
|
isReady: boolean;
|
||||||
|
queue: PgBoss | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
let initPromise: Promise<PgBoss> | null = null;
|
||||||
|
const state: QueueState = {
|
||||||
|
isReady: false,
|
||||||
|
queue: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function initQueue() {
|
||||||
|
if (state.isReady) {
|
||||||
|
return state.queue as PgBoss;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initPromise) {
|
||||||
|
return initPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
initPromise = (async () => {
|
||||||
|
const queue = new PgBoss({
|
||||||
|
connectionString: 'postgres://postgres:password@127.0.0.1:54321/queue',
|
||||||
|
|
||||||
|
schema: 'documenso_queue',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queue.start();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to start queue', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(jobHandlers).map(async ([job, jobHandler]) => {
|
||||||
|
await queue.work(job, jobHandler as WorkHandler<unknown>);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
state.isReady = true;
|
||||||
|
state.queue = queue;
|
||||||
|
|
||||||
|
return queue;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return initPromise;
|
||||||
|
}
|
||||||
85
packages/lib/server-only/queue/job.ts
Normal file
85
packages/lib/server-only/queue/job.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import type { WorkHandler } from 'pg-boss';
|
||||||
|
|
||||||
|
import type { MailOptions } from '@documenso/email/mailer';
|
||||||
|
import { mailer } from '@documenso/email/mailer';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { initQueue } from '.';
|
||||||
|
import type { CreateDocumentAuditLogDataOptions } from '../../utils/document-audit-logs';
|
||||||
|
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
|
import {
|
||||||
|
type SendDocumentOptions as SendCompletedDocumentOptions,
|
||||||
|
sendCompletedEmail,
|
||||||
|
} from '../document/send-completed-email';
|
||||||
|
import { type SendPendingEmailOptions, sendPendingEmail } from '../document/send-pending-email';
|
||||||
|
|
||||||
|
type JobOptions = {
|
||||||
|
'send-mail': MailOptions;
|
||||||
|
'send-completed-email': SendCompletedDocumentOptions;
|
||||||
|
'send-pending-email': SendPendingEmailOptions;
|
||||||
|
'create-document-audit-log': CreateDocumentAuditLogDataOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const jobHandlers: {
|
||||||
|
[K in keyof JobOptions]: WorkHandler<JobOptions[K]>;
|
||||||
|
} = {
|
||||||
|
'send-completed-email': async ({ id, name, data: { documentId, requestMetadata } }) => {
|
||||||
|
console.log('Running Queue: ', name, ' ', id);
|
||||||
|
|
||||||
|
await sendCompletedEmail({
|
||||||
|
documentId,
|
||||||
|
requestMetadata,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'send-pending-email': async ({ id, name, data: { documentId, recipientId } }) => {
|
||||||
|
console.log('Running Queue: ', name, ' ', id);
|
||||||
|
|
||||||
|
await sendPendingEmail({
|
||||||
|
documentId,
|
||||||
|
recipientId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'send-mail': async ({ id, name, data: { attachments, to, from, subject, html, text } }) => {
|
||||||
|
console.log('Running Queue: ', name, ' ', id);
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
subject,
|
||||||
|
html,
|
||||||
|
text,
|
||||||
|
attachments,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Audit Logs Queue
|
||||||
|
'create-document-audit-log': async ({
|
||||||
|
name,
|
||||||
|
data: { documentId, type, requestMetadata, user, data },
|
||||||
|
id,
|
||||||
|
}) => {
|
||||||
|
console.log('Running Queue: ', name, ' ', id);
|
||||||
|
|
||||||
|
await prisma.documentAuditLog.create({
|
||||||
|
data: createDocumentAuditLogData({
|
||||||
|
type,
|
||||||
|
documentId,
|
||||||
|
requestMetadata,
|
||||||
|
user,
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const queueJob = async ({
|
||||||
|
job,
|
||||||
|
args,
|
||||||
|
}: {
|
||||||
|
job: keyof JobOptions;
|
||||||
|
args?: JobOptions[keyof JobOptions];
|
||||||
|
}) => {
|
||||||
|
const queue = await initQueue();
|
||||||
|
|
||||||
|
await queue.send(job, args ?? {});
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import type { Team } from '@documenso/prisma/client';
|
|||||||
import { SendStatus } from '@documenso/prisma/client';
|
import { SendStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type DeleteRecipientOptions = {
|
export type DeleteRecipientOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
@@ -73,15 +73,15 @@ export const deleteRecipient = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedRecipient = await prisma.$transaction(async (tx) => {
|
const deletedRecipient = await prisma.recipient.delete({
|
||||||
const deleted = await tx.recipient.delete({
|
|
||||||
where: {
|
where: {
|
||||||
id: recipient.id,
|
id: recipient.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: 'RECIPIENT_DELETED',
|
type: 'RECIPIENT_DELETED',
|
||||||
documentId,
|
documentId,
|
||||||
user: {
|
user: {
|
||||||
@@ -96,10 +96,7 @@ export const deleteRecipient = async ({
|
|||||||
recipientRole: recipient.role,
|
recipientRole: recipient.role,
|
||||||
},
|
},
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
}),
|
},
|
||||||
});
|
|
||||||
|
|
||||||
return deleted;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deletedRecipient;
|
return deletedRecipient;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { RecipientRole } from '@documenso/prisma/client';
|
|||||||
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export interface SetRecipientsForDocumentOptions {
|
export interface SetRecipientsForDocumentOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
@@ -203,8 +204,9 @@ export const setRecipientsForDocument = async ({
|
|||||||
|
|
||||||
// Handle recipient updated audit log.
|
// Handle recipient updated audit log.
|
||||||
if (recipient._persisted && changes.length > 0) {
|
if (recipient._persisted && changes.length > 0) {
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
user,
|
||||||
@@ -213,14 +215,15 @@ export const setRecipientsForDocument = async ({
|
|||||||
changes,
|
changes,
|
||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle recipient created audit log.
|
// Handle recipient created audit log.
|
||||||
if (!recipient._persisted) {
|
if (!recipient._persisted) {
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_CREATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user,
|
user,
|
||||||
@@ -229,7 +232,7 @@ export const setRecipientsForDocument = async ({
|
|||||||
...baseAuditLog,
|
...baseAuditLog,
|
||||||
actionAuth: recipient.actionAuth || undefined,
|
actionAuth: recipient.actionAuth || undefined,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import type { RecipientRole, Team } from '@documenso/prisma/client';
|
|||||||
|
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { createDocumentAuditLogData, diffRecipientChanges } from '../../utils/document-audit-logs';
|
import { diffRecipientChanges } from '../../utils/document-audit-logs';
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type UpdateRecipientOptions = {
|
export type UpdateRecipientOptions = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
@@ -75,8 +76,7 @@ export const updateRecipient = async ({
|
|||||||
throw new Error('Recipient not found');
|
throw new Error('Recipient not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRecipient = await prisma.$transaction(async (tx) => {
|
const updatedRecipient = await prisma.recipient.update({
|
||||||
const persisted = await prisma.recipient.update({
|
|
||||||
where: {
|
where: {
|
||||||
id: recipient.id,
|
id: recipient.id,
|
||||||
},
|
},
|
||||||
@@ -87,11 +87,12 @@ export const updateRecipient = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const changes = diffRecipientChanges(recipient, persisted);
|
const changes = diffRecipientChanges(recipient, updatedRecipient);
|
||||||
|
|
||||||
if (changes.length > 0) {
|
if (changes.length > 0) {
|
||||||
await tx.documentAuditLog.create({
|
await queueJob({
|
||||||
data: createDocumentAuditLogData({
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
type: DOCUMENT_AUDIT_LOG_TYPE.RECIPIENT_UPDATED,
|
||||||
documentId: documentId,
|
documentId: documentId,
|
||||||
user: {
|
user: {
|
||||||
@@ -103,16 +104,15 @@ export const updateRecipient = async ({
|
|||||||
data: {
|
data: {
|
||||||
changes,
|
changes,
|
||||||
recipientId,
|
recipientId,
|
||||||
recipientEmail: persisted.email,
|
recipientEmail: updatedRecipient.email,
|
||||||
recipientName: persisted.name,
|
recipientName: updatedRecipient.name,
|
||||||
recipientRole: persisted.role,
|
recipientRole: updatedRecipient.role,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
return persisted;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return updatedRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
return updatedRecipient;
|
return updatedRecipient;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { createElement } from 'react';
|
|||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import { ConfirmTeamEmailTemplate } from '@documenso/email/templates/confirm-team-email';
|
import { ConfirmTeamEmailTemplate } from '@documenso/email/templates/confirm-team-email';
|
||||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||||
@@ -13,6 +12,8 @@ import { createTokenVerification } from '@documenso/lib/utils/token-verification
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { Prisma } from '@documenso/prisma/client';
|
import { Prisma } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type CreateTeamEmailVerificationOptions = {
|
export type CreateTeamEmailVerificationOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId: number;
|
teamId: number;
|
||||||
@@ -122,7 +123,9 @@ export const sendTeamEmailVerificationEmail = async (
|
|||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mailer.sendMail({
|
await queueJob({
|
||||||
|
job: 'send-mail',
|
||||||
|
args: {
|
||||||
to: email,
|
to: email,
|
||||||
from: {
|
from: {
|
||||||
name: FROM_NAME,
|
name: FROM_NAME,
|
||||||
@@ -131,5 +134,6 @@ export const sendTeamEmailVerificationEmail = async (
|
|||||||
subject: `A request to use your email has been initiated by ${teamName} on Documenso`,
|
subject: `A request to use your email has been initiated by ${teamName} on Documenso`,
|
||||||
html: render(template),
|
html: render(template),
|
||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { createElement } from 'react';
|
|||||||
|
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import type { TeamInviteEmailProps } from '@documenso/email/templates/team-invite';
|
import type { TeamInviteEmailProps } from '@documenso/email/templates/team-invite';
|
||||||
import { TeamInviteEmailTemplate } from '@documenso/email/templates/team-invite';
|
import { TeamInviteEmailTemplate } from '@documenso/email/templates/team-invite';
|
||||||
@@ -15,6 +14,8 @@ import { prisma } from '@documenso/prisma';
|
|||||||
import { TeamMemberInviteStatus } from '@documenso/prisma/client';
|
import { TeamMemberInviteStatus } from '@documenso/prisma/client';
|
||||||
import type { TCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
import type { TCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type CreateTeamMemberInvitesOptions = {
|
export type CreateTeamMemberInvitesOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
userName: string;
|
userName: string;
|
||||||
@@ -148,7 +149,9 @@ export const sendTeamMemberInviteEmail = async ({
|
|||||||
...emailTemplateOptions,
|
...emailTemplateOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mailer.sendMail({
|
await queueJob({
|
||||||
|
job: 'send-mail',
|
||||||
|
args: {
|
||||||
to: email,
|
to: email,
|
||||||
from: {
|
from: {
|
||||||
name: FROM_NAME,
|
name: FROM_NAME,
|
||||||
@@ -157,5 +160,6 @@ export const sendTeamMemberInviteEmail = async ({
|
|||||||
subject: `You have been invited to join ${emailTemplateOptions.teamName} on Documenso`,
|
subject: `You have been invited to join ${emailTemplateOptions.teamName} on Documenso`,
|
||||||
html: render(template),
|
html: render(template),
|
||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import { TeamEmailRemovedTemplate } from '@documenso/email/templates/team-email-removed';
|
import { TeamEmailRemovedTemplate } from '@documenso/email/templates/team-email-removed';
|
||||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||||
@@ -8,6 +7,8 @@ import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
|||||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type DeleteTeamEmailOptions = {
|
export type DeleteTeamEmailOptions = {
|
||||||
userId: number;
|
userId: number;
|
||||||
userEmail: string;
|
userEmail: string;
|
||||||
@@ -73,7 +74,9 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
|
|||||||
teamUrl: team.url,
|
teamUrl: team.url,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mailer.sendMail({
|
await queueJob({
|
||||||
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
to: {
|
to: {
|
||||||
address: team.owner.email,
|
address: team.owner.email,
|
||||||
name: team.owner.name ?? '',
|
name: team.owner.name ?? '',
|
||||||
@@ -85,6 +88,7 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
|
|||||||
subject: `Team email has been revoked for ${team.name}`,
|
subject: `Team email has been revoked for ${team.name}`,
|
||||||
html: render(template),
|
html: render(template),
|
||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Todo: Teams - Alert us.
|
// Todo: Teams - Alert us.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { createElement } from 'react';
|
import { createElement } from 'react';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import { TeamTransferRequestTemplate } from '@documenso/email/templates/team-transfer-request';
|
import { TeamTransferRequestTemplate } from '@documenso/email/templates/team-transfer-request';
|
||||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||||
@@ -8,6 +7,8 @@ import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
|||||||
import { createTokenVerification } from '@documenso/lib/utils/token-verification';
|
import { createTokenVerification } from '@documenso/lib/utils/token-verification';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { queueJob } from '../queue/job';
|
||||||
|
|
||||||
export type RequestTeamOwnershipTransferOptions = {
|
export type RequestTeamOwnershipTransferOptions = {
|
||||||
/**
|
/**
|
||||||
* The ID of the user initiating the transfer.
|
* The ID of the user initiating the transfer.
|
||||||
@@ -93,7 +94,9 @@ export const requestTeamOwnershipTransfer = async ({
|
|||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
|
|
||||||
await mailer.sendMail({
|
await queueJob({
|
||||||
|
job: 'create-document-audit-log',
|
||||||
|
args: {
|
||||||
to: newOwnerUser.email,
|
to: newOwnerUser.email,
|
||||||
from: {
|
from: {
|
||||||
name: FROM_NAME,
|
name: FROM_NAME,
|
||||||
@@ -102,6 +105,7 @@ export const requestTeamOwnershipTransfer = async ({
|
|||||||
subject: `You have been requested to take ownership of team ${team.name} on Documenso`,
|
subject: `You have been requested to take ownership of team ${team.name} on Documenso`,
|
||||||
html: render(template),
|
html: render(template),
|
||||||
text: render(template, { plainText: true }),
|
text: render(template, { plainText: true }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{ timeout: 30_000 },
|
{ timeout: 30_000 },
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
import { ZRecipientAuthOptionsSchema } from '../types/document-auth';
|
import { ZRecipientAuthOptionsSchema } from '../types/document-auth';
|
||||||
import type { RequestMetadata } from '../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../universal/extract-request-metadata';
|
||||||
|
|
||||||
type CreateDocumentAuditLogDataOptions<T = TDocumentAuditLog['type']> = {
|
export type CreateDocumentAuditLogDataOptions<T = TDocumentAuditLog['type']> = {
|
||||||
documentId: number;
|
documentId: number;
|
||||||
type: T;
|
type: T;
|
||||||
data: Extract<TDocumentAuditLog, { type: T }>['data'];
|
data: Extract<TDocumentAuditLog, { type: T }>['data'];
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export const adminRouter = router({
|
|||||||
try {
|
try {
|
||||||
return await findDocuments({ term, page, perPage });
|
return await findDocuments({ term, page, perPage });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to retrieve the documents. Please try again.',
|
message: 'We were unable to retrieve the documents. Please try again.',
|
||||||
@@ -44,6 +46,8 @@ export const adminRouter = router({
|
|||||||
try {
|
try {
|
||||||
return await updateUser({ id, name, email, roles });
|
return await updateUser({ id, name, email, roles });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to retrieve the specified account. Please try again.',
|
message: 'We were unable to retrieve the specified account. Please try again.',
|
||||||
@@ -59,6 +63,8 @@ export const adminRouter = router({
|
|||||||
try {
|
try {
|
||||||
return await updateRecipient({ id, name, email });
|
return await updateRecipient({ id, name, email });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to update the recipient provided.',
|
message: 'We were unable to update the recipient provided.',
|
||||||
@@ -79,6 +85,8 @@ export const adminRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to update the site setting provided.',
|
message: 'We were unable to update the site setting provided.',
|
||||||
@@ -95,6 +103,7 @@ export const adminRouter = router({
|
|||||||
return await sealDocument({ documentId: id, isResealing: true });
|
return await sealDocument({ documentId: id, isResealing: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('resealDocument error', err);
|
console.log('resealDocument error', err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to reseal the document provided.',
|
message: 'We were unable to reseal the document provided.',
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const apiTokenRouter = router({
|
|||||||
getTokens: authenticatedProcedure.query(async ({ ctx }) => {
|
getTokens: authenticatedProcedure.query(async ({ ctx }) => {
|
||||||
try {
|
try {
|
||||||
return await getUserTokens({ userId: ctx.user.id });
|
return await getUserTokens({ userId: ctx.user.id });
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to find your API tokens. Please try again.',
|
message: 'We were unable to find your API tokens. Please try again.',
|
||||||
@@ -34,7 +36,9 @@ export const apiTokenRouter = router({
|
|||||||
id,
|
id,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to find this API token. Please try again.',
|
message: 'We were unable to find this API token. Please try again.',
|
||||||
@@ -54,7 +58,9 @@ export const apiTokenRouter = router({
|
|||||||
tokenName,
|
tokenName,
|
||||||
expiresIn: expirationDate,
|
expiresIn: expirationDate,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to create an API token. Please try again.',
|
message: 'We were unable to create an API token. Please try again.',
|
||||||
@@ -73,7 +79,9 @@ export const apiTokenRouter = router({
|
|||||||
teamId,
|
teamId,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to delete this API Token. Please try again.',
|
message: 'We were unable to delete this API Token. Please try again.',
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ export const documentRouter = router({
|
|||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
if (err instanceof TRPCError) {
|
if (err instanceof TRPCError) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -222,6 +224,7 @@ export const documentRouter = router({
|
|||||||
|
|
||||||
const userId = ctx.user.id;
|
const userId = ctx.user.id;
|
||||||
|
|
||||||
|
try {
|
||||||
return await updateTitle({
|
return await updateTitle({
|
||||||
title,
|
title,
|
||||||
userId,
|
userId,
|
||||||
@@ -229,6 +232,11 @@ export const documentRouter = router({
|
|||||||
documentId,
|
documentId,
|
||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setPasswordForDocument: authenticatedProcedure
|
setPasswordForDocument: authenticatedProcedure
|
||||||
@@ -347,7 +355,9 @@ export const documentRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
});
|
});
|
||||||
return documents;
|
return documents;
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We are unable to search for documents. Please try again later.',
|
message: 'We are unable to search for documents. Please try again later.',
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const fieldRouter = router({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { templateId, fields } = input;
|
const { templateId, fields } = input;
|
||||||
|
|
||||||
|
try {
|
||||||
await setFieldsForTemplate({
|
await setFieldsForTemplate({
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
templateId,
|
templateId,
|
||||||
@@ -66,6 +67,11 @@ export const fieldRouter = router({
|
|||||||
pageHeight: field.pageHeight,
|
pageHeight: field.pageHeight,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
signFieldWithToken: procedure
|
signFieldWithToken: procedure
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ export const profileRouter = router({
|
|||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to find user security audit logs. Please try again.',
|
message: 'We were unable to find user security audit logs. Please try again.',
|
||||||
@@ -50,6 +52,8 @@ export const profileRouter = router({
|
|||||||
|
|
||||||
return await getUserById({ id });
|
return await getUserById({ id });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to retrieve the specified account. Please try again.',
|
message: 'We were unable to retrieve the specified account. Please try again.',
|
||||||
@@ -108,6 +112,8 @@ export const profileRouter = router({
|
|||||||
|
|
||||||
return { success: true, url: user.url };
|
return { success: true, url: user.url };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
if (error.code !== AppErrorCode.UNKNOWN_ERROR) {
|
if (error.code !== AppErrorCode.UNKNOWN_ERROR) {
|
||||||
@@ -135,6 +141,8 @@ export const profileRouter = router({
|
|||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
let message =
|
let message =
|
||||||
'We were unable to update your profile. Please review the information you provided and try again.';
|
'We were unable to update your profile. Please review the information you provided and try again.';
|
||||||
|
|
||||||
@@ -171,6 +179,8 @@ export const profileRouter = router({
|
|||||||
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
requestMetadata: extractNextApiRequestMetadata(ctx.req),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
let message = 'We were unable to reset your password. Please try again.';
|
let message = 'We were unable to reset your password. Please try again.';
|
||||||
|
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
@@ -192,6 +202,8 @@ export const profileRouter = router({
|
|||||||
|
|
||||||
return await sendConfirmationToken({ email });
|
return await sendConfirmationToken({ email });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
let message = 'We were unable to send a confirmation email. Please try again.';
|
let message = 'We were unable to send a confirmation email. Please try again.';
|
||||||
|
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
@@ -211,6 +223,8 @@ export const profileRouter = router({
|
|||||||
id: ctx.user.id,
|
id: ctx.user.id,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
let message = 'We were unable to delete your account. Please try again.';
|
let message = 'We were unable to delete your account. Please try again.';
|
||||||
|
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { createElement } from 'react';
|
|||||||
|
|
||||||
import { PDFDocument } from 'pdf-lib';
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
import { mailer } from '@documenso/email/mailer';
|
|
||||||
import { renderAsync } from '@documenso/email/render';
|
import { renderAsync } from '@documenso/email/render';
|
||||||
import { DocumentSelfSignedEmailTemplate } from '@documenso/email/templates/document-self-signed';
|
import { DocumentSelfSignedEmailTemplate } from '@documenso/email/templates/document-self-signed';
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/constants/email';
|
import { FROM_ADDRESS, FROM_NAME, SERVICE_USER_EMAIL } from '@documenso/lib/constants/email';
|
||||||
import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in-pdf';
|
import { insertFieldInPDF } from '@documenso/lib/server-only/pdf/insert-field-in-pdf';
|
||||||
|
import { queueJob } from '@documenso/lib/server-only/queue/job';
|
||||||
import { alphaid } from '@documenso/lib/universal/id';
|
import { alphaid } from '@documenso/lib/universal/id';
|
||||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||||
import { putFile } from '@documenso/lib/universal/upload/put-file';
|
import { putFile } from '@documenso/lib/universal/upload/put-file';
|
||||||
@@ -29,6 +29,7 @@ export const singleplayerRouter = router({
|
|||||||
createSinglePlayerDocument: procedure
|
createSinglePlayerDocument: procedure
|
||||||
.input(ZCreateSinglePlayerDocumentMutationSchema)
|
.input(ZCreateSinglePlayerDocumentMutationSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
const { signer, fields, documentData, documentName } = input;
|
const { signer, fields, documentData, documentName } = input;
|
||||||
|
|
||||||
const document = await getFile({
|
const document = await getFile({
|
||||||
@@ -159,7 +160,9 @@ export const singleplayerRouter = router({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Send email to signer.
|
// Send email to signer.
|
||||||
await mailer.sendMail({
|
await queueJob({
|
||||||
|
job: 'send-mail',
|
||||||
|
args: {
|
||||||
to: {
|
to: {
|
||||||
address: signer.email,
|
address: signer.email,
|
||||||
name: signer.name,
|
name: signer.name,
|
||||||
@@ -172,8 +175,14 @@ export const singleplayerRouter = router({
|
|||||||
html,
|
html,
|
||||||
text,
|
text,
|
||||||
attachments: [{ content: signedPdfBuffer, filename: documentName }],
|
attachments: [{ content: signedPdfBuffer, filename: documentName }],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return token;
|
return token;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export const templateRouter = router({
|
|||||||
recipients: input.recipients,
|
recipients: input.recipients,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to create this document. Please try again later.',
|
message: 'We were unable to create this document. Please try again later.',
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export const webhookRouter = router({
|
|||||||
try {
|
try {
|
||||||
return await getWebhooksByUserId(ctx.user.id);
|
return await getWebhooksByUserId(ctx.user.id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to fetch your webhooks. Please try again later.',
|
message: 'We were unable to fetch your webhooks. Please try again later.',
|
||||||
@@ -36,6 +38,8 @@ export const webhookRouter = router({
|
|||||||
try {
|
try {
|
||||||
return await getWebhooksByTeamId(teamId, ctx.user.id);
|
return await getWebhooksByTeamId(teamId, ctx.user.id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to fetch your webhooks. Please try again later.',
|
message: 'We were unable to fetch your webhooks. Please try again later.',
|
||||||
@@ -55,6 +59,8 @@ export const webhookRouter = router({
|
|||||||
teamId,
|
teamId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to fetch your webhook. Please try again later.',
|
message: 'We were unable to fetch your webhook. Please try again later.',
|
||||||
@@ -77,6 +83,8 @@ export const webhookRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to create this webhook. Please try again later.',
|
message: 'We were unable to create this webhook. Please try again later.',
|
||||||
@@ -96,6 +104,8 @@ export const webhookRouter = router({
|
|||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to create this webhook. Please try again later.',
|
message: 'We were unable to create this webhook. Please try again later.',
|
||||||
@@ -116,6 +126,8 @@ export const webhookRouter = router({
|
|||||||
teamId,
|
teamId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'We were unable to create this webhook. Please try again later.',
|
message: 'We were unable to create this webhook. Please try again later.',
|
||||||
|
|||||||
@@ -223,6 +223,10 @@ export const AddSettingsFormPartial = ({
|
|||||||
<strong>Require passkey</strong> - The recipient must have an account
|
<strong>Require passkey</strong> - The recipient must have an account
|
||||||
and passkey configured via their settings
|
and passkey configured via their settings
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Require 2FA</strong> - The recipient must have an account and
|
||||||
|
2FA enabled via their settings
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>None</strong> - No authentication required
|
<strong>None</strong> - No authentication required
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -291,6 +291,10 @@ export const AddSignersFormPartial = ({
|
|||||||
<strong>Require passkey</strong> - The recipient must have
|
<strong>Require passkey</strong> - The recipient must have
|
||||||
an account and passkey configured via their settings
|
an account and passkey configured via their settings
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Require 2FA</strong> - The recipient must have an
|
||||||
|
account and 2FA enabled via their settings
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>None</strong> - No authentication required
|
<strong>None</strong> - No authentication required
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -93,6 +93,7 @@
|
|||||||
"NEXT_PRIVATE_STRIPE_API_KEY",
|
"NEXT_PRIVATE_STRIPE_API_KEY",
|
||||||
"NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET",
|
"NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET",
|
||||||
"NEXT_PRIVATE_GITHUB_TOKEN",
|
"NEXT_PRIVATE_GITHUB_TOKEN",
|
||||||
|
"NEXT_RUNTIME",
|
||||||
"CI",
|
"CI",
|
||||||
"VERCEL",
|
"VERCEL",
|
||||||
"VERCEL_ENV",
|
"VERCEL_ENV",
|
||||||
|
|||||||
Reference in New Issue
Block a user