mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 19:21:39 +10:00
fix: wip
This commit is contained in:
@ -37,7 +37,7 @@ import {
|
|||||||
import { Input } from '@documenso/ui/primitives/input';
|
import { Input } from '@documenso/ui/primitives/input';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
export type CreatePasskeyDialogProps = {
|
export type PasskeyCreateDialogProps = {
|
||||||
trigger?: React.ReactNode;
|
trigger?: React.ReactNode;
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
||||||
@ -50,7 +50,7 @@ type TCreatePasskeyFormSchema = z.infer<typeof ZCreatePasskeyFormSchema>;
|
|||||||
|
|
||||||
const parser = new UAParser();
|
const parser = new UAParser();
|
||||||
|
|
||||||
export const CreatePasskeyDialog = ({ trigger, onSuccess, ...props }: CreatePasskeyDialogProps) => {
|
export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCreateDialogProps) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [formError, setFormError] = useState<string | null>(null);
|
const [formError, setFormError] = useState<string | null>(null);
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ import { FilePlus, Loader } from 'lucide-react';
|
|||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
|
|
||||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||||
import { AppError } from '@documenso/lib/errors/app-error';
|
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import {
|
import {
|
||||||
@ -48,27 +48,7 @@ export const TemplateCreateDialog = ({ templateRootPath }: TemplateCreateDialogP
|
|||||||
setIsUploadingFile(true);
|
setIsUploadingFile(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Todo
|
const response = await putPdfFile(file);
|
||||||
// const { type, data } = await putPdfFile(file);
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
|
|
||||||
const response = await fetch('/api/file', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.then(async (res) => await res.json())
|
|
||||||
.catch((e) => {
|
|
||||||
console.error('Upload failed:', e);
|
|
||||||
throw new AppError('UPLOAD_FAILED');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Why do we run this twice?
|
|
||||||
// const { id: templateDocumentDataId } = await createDocumentData({
|
|
||||||
// type: response.type,
|
|
||||||
// data: response.data,
|
|
||||||
// });
|
|
||||||
|
|
||||||
const { id } = await createTemplate({
|
const { id } = await createTemplate({
|
||||||
title: file.name,
|
title: file.name,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
|
TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX,
|
||||||
} from '@documenso/lib/constants/template';
|
} from '@documenso/lib/constants/template';
|
||||||
import { AppError } from '@documenso/lib/errors/app-error';
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
|
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
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';
|
||||||
@ -150,22 +151,7 @@ export function TemplateUseDialog({
|
|||||||
let customDocumentDataId: string | undefined = undefined;
|
let customDocumentDataId: string | undefined = undefined;
|
||||||
|
|
||||||
if (data.useCustomDocument && data.customDocumentData) {
|
if (data.useCustomDocument && data.customDocumentData) {
|
||||||
// const customDocumentData = await putPdfFile(data.customDocumentData);
|
const customDocumentData = await putPdfFile(data.customDocumentData);
|
||||||
// Todo
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', data.customDocumentData);
|
|
||||||
|
|
||||||
const customDocumentData = await fetch('/api/file', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.then(async (res) => await res.json())
|
|
||||||
.catch((e) => {
|
|
||||||
console.error('Upload failed:', e);
|
|
||||||
throw new AppError('UPLOAD_FAILED');
|
|
||||||
});
|
|
||||||
|
|
||||||
customDocumentDataId = customDocumentData.id;
|
customDocumentDataId = customDocumentData.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,12 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { User } from '@prisma/client';
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { authClient } from '@documenso/auth/client';
|
import { authClient } from '@documenso/auth/client';
|
||||||
|
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||||
import { AppError } from '@documenso/lib/errors/app-error';
|
import { AppError } from '@documenso/lib/errors/app-error';
|
||||||
import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@ -38,7 +38,7 @@ export type TPasswordFormSchema = z.infer<typeof ZPasswordFormSchema>;
|
|||||||
|
|
||||||
export type PasswordFormProps = {
|
export type PasswordFormProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
user: User;
|
user: SessionUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PasswordForm = ({ className }: PasswordFormProps) => {
|
export const PasswordForm = ({ className }: PasswordFormProps) => {
|
||||||
|
|||||||
@ -97,7 +97,7 @@ export const SignInForm = ({
|
|||||||
|
|
||||||
const [isPasskeyLoading, setIsPasskeyLoading] = useState(false);
|
const [isPasskeyLoading, setIsPasskeyLoading] = useState(false);
|
||||||
|
|
||||||
const redirectUrl = useMemo(() => {
|
const redirectPath = useMemo(() => {
|
||||||
// Handle SSR
|
// Handle SSR
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return LOGIN_REDIRECT_PATH;
|
return LOGIN_REDIRECT_PATH;
|
||||||
@ -171,7 +171,7 @@ export const SignInForm = ({
|
|||||||
await authClient.passkey.signIn({
|
await authClient.passkey.signIn({
|
||||||
credential: JSON.stringify(credential),
|
credential: JSON.stringify(credential),
|
||||||
csrfToken: sessionId,
|
csrfToken: sessionId,
|
||||||
redirectUrl,
|
redirectPath,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setIsPasskeyLoading(false);
|
setIsPasskeyLoading(false);
|
||||||
@ -211,7 +211,7 @@ export const SignInForm = ({
|
|||||||
password,
|
password,
|
||||||
totpCode,
|
totpCode,
|
||||||
backupCode,
|
backupCode,
|
||||||
redirectUrl,
|
redirectPath,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import { Loader } from 'lucide-react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||||
|
import { putFile } from '@documenso/lib/universal/upload/put-file';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
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';
|
||||||
@ -78,8 +80,7 @@ export function TeamBrandingPreferencesForm({ team, settings }: TeamBrandingPref
|
|||||||
let uploadedBrandingLogo = settings?.brandingLogo;
|
let uploadedBrandingLogo = settings?.brandingLogo;
|
||||||
|
|
||||||
if (brandingLogo) {
|
if (brandingLogo) {
|
||||||
// Todo
|
uploadedBrandingLogo = JSON.stringify(await putFile(brandingLogo));
|
||||||
// uploadedBrandingLogo = JSON.stringify(await putFile(brandingLogo));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (brandingLogo === null) {
|
if (brandingLogo === null) {
|
||||||
@ -116,27 +117,13 @@ export function TeamBrandingPreferencesForm({ team, settings }: TeamBrandingPref
|
|||||||
const file = JSON.parse(settings.brandingLogo);
|
const file = JSON.parse(settings.brandingLogo);
|
||||||
|
|
||||||
if ('type' in file && 'data' in file) {
|
if ('type' in file && 'data' in file) {
|
||||||
// Todo
|
void getFile(file).then((binaryData) => {
|
||||||
// Todo
|
const objectUrl = URL.createObjectURL(new Blob([binaryData]));
|
||||||
// Todo
|
|
||||||
void fetch(`/api/file?key=${file.key}`, {
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.then(async (res) => await res.json())
|
|
||||||
.then((data) => {
|
|
||||||
const objectUrl = URL.createObjectURL(new Blob([data.binaryData]));
|
|
||||||
|
|
||||||
setPreviewUrl(objectUrl);
|
setPreviewUrl(objectUrl);
|
||||||
setHasLoadedPreview(true);
|
setHasLoadedPreview(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// void getFile(file).then((binaryData) => {
|
|
||||||
// const objectUrl = URL.createObjectURL(new Blob([binaryData]));
|
|
||||||
|
|
||||||
// setPreviewUrl(objectUrl);
|
|
||||||
// setHasLoadedPreview(true);
|
|
||||||
// });
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { type HTMLAttributes, useEffect, useState } from 'react';
|
import { type HTMLAttributes, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import type { User } from '@prisma/client';
|
|
||||||
import { MenuIcon, SearchIcon } from 'lucide-react';
|
import { MenuIcon, SearchIcon } from 'lucide-react';
|
||||||
import { Link, useLocation, useParams } from 'react-router';
|
import { Link, useLocation, useParams } from 'react-router';
|
||||||
|
|
||||||
|
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||||
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||||
import { getRootHref } from '@documenso/lib/utils/params';
|
import { getRootHref } from '@documenso/lib/utils/params';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@ -16,7 +16,7 @@ import { AppNavMobile } from './app-nav-mobile';
|
|||||||
import { MenuSwitcher } from './menu-switcher';
|
import { MenuSwitcher } from './menu-switcher';
|
||||||
|
|
||||||
export type HeaderProps = HTMLAttributes<HTMLDivElement> & {
|
export type HeaderProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
user: User;
|
user: SessionUser;
|
||||||
teams: TGetTeamsResponse;
|
teams: TGetTeamsResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -109,14 +109,12 @@ export const DocumentSigningAuth2FA = ({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{user?.identityProvider === 'DOCUMENSO' && (
|
|
||||||
<p className="mt-2">
|
<p className="mt-2">
|
||||||
<Trans>
|
<Trans>
|
||||||
By enabling 2FA, you will be required to enter a code from your authenticator app
|
By enabling 2FA, you will be required to enter a code from your authenticator app
|
||||||
every time you sign in.
|
every time you sign in using email password.
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
)}
|
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const DocumentSigningAuthAccount = ({
|
|||||||
setIsSigningOut(true);
|
setIsSigningOut(true);
|
||||||
|
|
||||||
await authClient.signOut({
|
await authClient.signOut({
|
||||||
redirectUrl: `/signin#email=${email}`,
|
redirectPath: `/signin#email=${email}`,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
setIsSigningOut(false);
|
setIsSigningOut(false);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export const DocumentSigningAuthPageView = ({
|
|||||||
setIsSigningOut(true);
|
setIsSigningOut(true);
|
||||||
|
|
||||||
await authClient.signOut({
|
await authClient.signOut({
|
||||||
redirectUrl: emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`,
|
redirectPath: emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@documenso/ui/primitives/select';
|
} from '@documenso/ui/primitives/select';
|
||||||
|
|
||||||
import { CreatePasskeyDialog } from '~/components/dialogs/create-passkey-dialog';
|
import { PasskeyCreateDialog } from '~/components/dialogs/passkey-create-dialog';
|
||||||
|
|
||||||
import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider';
|
import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider';
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ export const DocumentSigningAuthPasskey = ({
|
|||||||
<Trans>Cancel</Trans>
|
<Trans>Cancel</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<CreatePasskeyDialog
|
<PasskeyCreateDialog
|
||||||
onSuccess={async () => refetchPasskeys()}
|
onSuccess={async () => refetchPasskeys()}
|
||||||
trigger={
|
trigger={
|
||||||
<Button>
|
<Button>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { type Document, FieldType, type Passkey, type Recipient, type User } from '@prisma/client';
|
import { type Document, FieldType, type Passkey, type Recipient } from '@prisma/client';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
|
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||||
import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth';
|
import { MAXIMUM_PASSKEYS } from '@documenso/lib/constants/auth';
|
||||||
import type {
|
import type {
|
||||||
TDocumentAuthOptions,
|
TDocumentAuthOptions,
|
||||||
@ -40,7 +41,7 @@ export type DocumentSigningAuthContextValue = {
|
|||||||
passkeyData: PasskeyData;
|
passkeyData: PasskeyData;
|
||||||
preferredPasskeyId: string | null;
|
preferredPasskeyId: string | null;
|
||||||
setPreferredPasskeyId: (_value: string | null) => void;
|
setPreferredPasskeyId: (_value: string | null) => void;
|
||||||
user?: User | null;
|
user?: SessionUser | null;
|
||||||
refetchPasskeys: () => Promise<void>;
|
refetchPasskeys: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,7 +64,7 @@ export const useRequiredDocumentSigningAuthContext = () => {
|
|||||||
export interface DocumentSigningAuthProviderProps {
|
export interface DocumentSigningAuthProviderProps {
|
||||||
documentAuthOptions: Document['authOptions'];
|
documentAuthOptions: Document['authOptions'];
|
||||||
recipient: Recipient;
|
recipient: Recipient;
|
||||||
user?: User | null;
|
user?: SessionUser | null;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,10 @@ import type { HTMLAttributes } from 'react';
|
|||||||
import type { MessageDescriptor } from '@lingui/core';
|
import type { MessageDescriptor } from '@lingui/core';
|
||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import type { ExtendedDocumentStatus } from '@prisma/types/extended-document-status';
|
|
||||||
import { CheckCircle2, Clock, File } from 'lucide-react';
|
import { CheckCircle2, Clock, File } from 'lucide-react';
|
||||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||||
|
|
||||||
|
import type { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
|
||||||
import { SignatureIcon } from '@documenso/ui/icons/signature';
|
import { SignatureIcon } from '@documenso/ui/icons/signature';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { useSession } from '@documenso/lib/client-only/providers/session';
|
|||||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { trpc } from '@documenso/trpc/react';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
@ -62,30 +63,11 @@ export const DocumentUploadDropzone = ({ className }: DocumentUploadDropzoneProp
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
// Todo
|
const response = await putPdfFile(file);
|
||||||
// const { type, data } = await putPdfFile(file);
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
|
|
||||||
const response = await fetch('/api/file', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.then(async (res) => res.json())
|
|
||||||
.catch((e) => {
|
|
||||||
console.error('Upload failed:', e);
|
|
||||||
throw new AppError('UPLOAD_FAILED');
|
|
||||||
});
|
|
||||||
|
|
||||||
// const { id: documentDataId } = await createDocumentData({
|
|
||||||
// type,
|
|
||||||
// data,
|
|
||||||
// });
|
|
||||||
|
|
||||||
const { id } = await createDocument({
|
const { id } = await createDocument({
|
||||||
title: file.name,
|
title: file.name,
|
||||||
documentDataId: response.id, // todo
|
documentDataId: response.id,
|
||||||
timezone: userTimezone,
|
timezone: userTimezone,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,12 @@ import { useState } from 'react';
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import type { User } from '@prisma/client';
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { CheckCircle2, ChevronsUpDown, Plus, Settings2 } from 'lucide-react';
|
import { CheckCircle2, ChevronsUpDown, Plus, Settings2 } from 'lucide-react';
|
||||||
import { Link, useLocation } from 'react-router';
|
import { Link, useLocation } from 'react-router';
|
||||||
|
|
||||||
import { authClient } from '@documenso/auth/client';
|
import { authClient } from '@documenso/auth/client';
|
||||||
|
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||||
import { TEAM_MEMBER_ROLE_MAP, TEAM_URL_REGEX } from '@documenso/lib/constants/teams';
|
import { TEAM_MEMBER_ROLE_MAP, TEAM_URL_REGEX } from '@documenso/lib/constants/teams';
|
||||||
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
|
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
|
||||||
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
||||||
@ -32,7 +32,7 @@ import {
|
|||||||
const MotionLink = motion(Link);
|
const MotionLink = motion(Link);
|
||||||
|
|
||||||
export type MenuSwitcherProps = {
|
export type MenuSwitcherProps = {
|
||||||
user: User;
|
user: SessionUser;
|
||||||
teams: TGetTeamsResponse;
|
teams: TGetTeamsResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { msg } from '@lingui/core/macro';
|
|
||||||
import { useLingui } from '@lingui/react';
|
|
||||||
|
|
||||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
|
||||||
|
|
||||||
import { ClaimPublicProfileDialogForm } from '~/components/forms/public-profile-claim-dialog';
|
|
||||||
|
|
||||||
export const UpcomingProfileClaimTeaser = () => {
|
|
||||||
const { user } = useSession();
|
|
||||||
|
|
||||||
const { _ } = useLingui();
|
|
||||||
const { toast } = useToast();
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [claimed, setClaimed] = useState(false);
|
|
||||||
|
|
||||||
const onOpenChange = useCallback(
|
|
||||||
(open: boolean) => {
|
|
||||||
if (!open && !claimed) {
|
|
||||||
toast({
|
|
||||||
title: _(msg`Claim your profile later`),
|
|
||||||
description: _(
|
|
||||||
msg`You can claim your profile later on by going to your profile settings!`,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setOpen(open);
|
|
||||||
localStorage.setItem('app.hasShownProfileClaimDialog', 'true');
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[claimed, toast],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const hasShownProfileClaimDialog =
|
|
||||||
localStorage.getItem('app.hasShownProfileClaimDialog') === 'true';
|
|
||||||
|
|
||||||
if (!user.url && !hasShownProfileClaimDialog) {
|
|
||||||
onOpenChange(true);
|
|
||||||
}
|
|
||||||
}, [onOpenChange, user.url]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ClaimPublicProfileDialogForm
|
|
||||||
open={open}
|
|
||||||
onOpenChange={onOpenChange}
|
|
||||||
onClaimed={() => setClaimed(true)}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -5,8 +5,8 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle } from 'lucide-react';
|
||||||
|
|
||||||
|
import { authClient } from '@documenso/auth/client';
|
||||||
import { ONE_DAY, ONE_SECOND } from '@documenso/lib/constants/time';
|
import { ONE_DAY, ONE_SECOND } from '@documenso/lib/constants/time';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -27,18 +27,20 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [isPending, setIsPending] = useState(false);
|
||||||
|
|
||||||
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
|
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
|
||||||
|
|
||||||
// Todo
|
|
||||||
const { mutateAsync: sendConfirmationEmail, isPending } =
|
|
||||||
trpc.profile.sendConfirmationEmail.useMutation();
|
|
||||||
|
|
||||||
const onResendConfirmationEmail = async () => {
|
const onResendConfirmationEmail = async () => {
|
||||||
|
if (isPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPending(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsButtonDisabled(true);
|
setIsButtonDisabled(true);
|
||||||
|
await authClient.emailPassword.resendVerifyEmail({ email: email });
|
||||||
await sendConfirmationEmail({ email: email });
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Success`),
|
title: _(msg`Success`),
|
||||||
@ -56,6 +58,8 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
|||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsPending(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -196,7 +196,7 @@ export const DocumentsTableActionDropdown = ({ row }: DocumentsTableActionDropdo
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DocumentResendDialog document={row} recipients={nonSignedRecipients} team={team} />
|
<DocumentResendDialog document={row} recipients={nonSignedRecipients} />
|
||||||
|
|
||||||
<DocumentShareButton
|
<DocumentShareButton
|
||||||
documentId={row.id}
|
documentId={row.id}
|
||||||
|
|||||||
@ -180,5 +180,11 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|||||||
|
|
||||||
const errorCode = isRouteErrorResponse(error) ? error.status : 500;
|
const errorCode = isRouteErrorResponse(error) ? error.status : 500;
|
||||||
|
|
||||||
|
if (isRouteErrorResponse(error)) {
|
||||||
|
console.log(error.data);
|
||||||
|
console.log(error.status);
|
||||||
|
console.log(error.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
return <GenericErrorLayout errorCode={errorCode} />;
|
return <GenericErrorLayout errorCode={errorCode} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@documenso/ui/primitives/table';
|
} from '@documenso/ui/primitives/table';
|
||||||
|
|
||||||
import type { Route } from './+types/_index';
|
import type { Route } from './+types/subscriptions';
|
||||||
|
|
||||||
export async function loader() {
|
export async function loader() {
|
||||||
const subscriptions = await findSubscriptions();
|
const subscriptions = await findSubscriptions();
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { findUsers } from '@documenso/lib/server-only/user/get-all-users';
|
|||||||
|
|
||||||
import { AdminDashboardUsersTable } from '~/components/tables/admin-dashboard-users-table';
|
import { AdminDashboardUsersTable } from '~/components/tables/admin-dashboard-users-table';
|
||||||
|
|
||||||
import type { Route } from './+types/users';
|
import type { Route } from './+types/users._index';
|
||||||
|
|
||||||
export async function loader({ request }: Route.LoaderArgs) {
|
export async function loader({ request }: Route.LoaderArgs) {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
|
|||||||
@ -137,7 +137,8 @@ export default function DocumentPage() {
|
|||||||
|
|
||||||
const { recipients, documentData, documentMeta } = document;
|
const { recipients, documentData, documentMeta } = document;
|
||||||
|
|
||||||
const isDocumentHistoryEnabled = false; // Todo: Was flag
|
// This was a feature flag. Leave to false since it's not ready.
|
||||||
|
const isDocumentHistoryEnabled = false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export default function PublicProfilePage({ loaderData }: Route.ComponentProps)
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const user = useSession();
|
const { user } = useSession();
|
||||||
const team = useOptionalCurrentTeam();
|
const team = useOptionalCurrentTeam();
|
||||||
|
|
||||||
const [isPublicProfileVisible, setIsPublicProfileVisible] = useState(profile.enabled);
|
const [isPublicProfileVisible, setIsPublicProfileVisible] = useState(profile.enabled);
|
||||||
|
|||||||
@ -2,8 +2,10 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import { getLoaderSession } from 'server/utils/get-loader-session';
|
||||||
|
|
||||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||||
|
import { prisma } from '@documenso/prisma';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
|
|
||||||
@ -13,22 +15,46 @@ import { ViewRecoveryCodesDialog } from '~/components/forms/2fa/view-recovery-co
|
|||||||
import { PasswordForm } from '~/components/forms/password';
|
import { PasswordForm } from '~/components/forms/password';
|
||||||
import { SettingsHeader } from '~/components/general/settings-header';
|
import { SettingsHeader } from '~/components/general/settings-header';
|
||||||
|
|
||||||
|
import type { Route } from './+types';
|
||||||
|
|
||||||
export function meta() {
|
export function meta() {
|
||||||
return [{ title: 'Security' }];
|
return [{ title: 'Security' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SettingsSecurity() {
|
export async function loader() {
|
||||||
|
const { user } = getLoaderSession();
|
||||||
|
|
||||||
|
const accounts = await prisma.account.findMany({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
provider: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const providers = accounts.map((account) => account.provider);
|
||||||
|
|
||||||
|
return {
|
||||||
|
providers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
|
||||||
|
const { providers } = loaderData;
|
||||||
|
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { user } = useSession();
|
const { user } = useSession();
|
||||||
|
|
||||||
|
const hasEmailPasswordAccount = providers.includes('DOCUMENSO');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
title={_(msg`Security`)}
|
title={_(msg`Security`)}
|
||||||
subtitle={_(msg`Here you can manage your password and security settings.`)}
|
subtitle={_(msg`Here you can manage your password and security settings.`)}
|
||||||
/>
|
/>
|
||||||
|
{hasEmailPasswordAccount && (
|
||||||
{user.identityProvider === 'DOCUMENSO' && (
|
|
||||||
<>
|
<>
|
||||||
<PasswordForm user={user} />
|
<PasswordForm user={user} />
|
||||||
|
|
||||||
@ -46,7 +72,7 @@ export default function SettingsSecurity() {
|
|||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
|
|
||||||
<AlertDescription className="mr-4">
|
<AlertDescription className="mr-4">
|
||||||
{user.identityProvider === 'DOCUMENSO' ? (
|
{hasEmailPasswordAccount ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
Add an authenticator to serve as a secondary authentication method when signing in,
|
Add an authenticator to serve as a secondary authentication method when signing in,
|
||||||
or when signing documents.
|
or when signing documents.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { CreatePasskeyDialog } from '~/components/dialogs/create-passkey-dialog';
|
import { PasskeyCreateDialog } from '~/components/dialogs/passkey-create-dialog';
|
||||||
import { SettingsHeader } from '~/components/general/settings-header';
|
import { SettingsHeader } from '~/components/general/settings-header';
|
||||||
import { SettingsSecurityPasskeyTable } from '~/components/tables/settings-security-passkey-table';
|
import { SettingsSecurityPasskeyTable } from '~/components/tables/settings-security-passkey-table';
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ export default function SettingsPasskeys() {
|
|||||||
subtitle={_(msg`Manage your passkeys.`)}
|
subtitle={_(msg`Manage your passkeys.`)}
|
||||||
hideDivider={true}
|
hideDivider={true}
|
||||||
>
|
>
|
||||||
<CreatePasskeyDialog />
|
<PasskeyCreateDialog />
|
||||||
</SettingsHeader>
|
</SettingsHeader>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|||||||
@ -59,10 +59,10 @@ export async function loader({ params }: Route.LoaderArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return superLoaderJson({
|
return superLoaderJson({
|
||||||
isAccessAuthValid: true as const,
|
isAccessAuthValid: true,
|
||||||
template,
|
template,
|
||||||
directTemplateRecipient,
|
directTemplateRecipient,
|
||||||
});
|
} as const);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DirectTemplatePage() {
|
export default function DirectTemplatePage() {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "sh .bin/build.sh",
|
"build": "sh .bin/build.sh",
|
||||||
"build:app": "cross-env NODE_ENV=production react-router build",
|
"build:app": "npm run typecheck && cross-env NODE_ENV=production react-router build",
|
||||||
"build:server": "cross-env NODE_ENV=production rollup -c rollup.config.mjs",
|
"build:server": "cross-env NODE_ENV=production rollup -c rollup.config.mjs",
|
||||||
"dev": "npm run with:env -- react-router dev",
|
"dev": "npm run with:env -- react-router dev",
|
||||||
"start": "npm run with:env -- cross-env NODE_ENV=production node build/server/main.js",
|
"start": "npm run with:env -- cross-env NODE_ENV=production node build/server/main.js",
|
||||||
@ -26,7 +26,6 @@
|
|||||||
"@epic-web/remember": "^1.1.0",
|
"@epic-web/remember": "^1.1.0",
|
||||||
"@hono/node-server": "^1.13.7",
|
"@hono/node-server": "^1.13.7",
|
||||||
"@hono/trpc-server": "^0.3.4",
|
"@hono/trpc-server": "^0.3.4",
|
||||||
"@hono/zod-validator": "^0.4.2",
|
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.0",
|
||||||
"@lingui/core": "^5.2.0",
|
"@lingui/core": "^5.2.0",
|
||||||
"@lingui/detect-locale": "^5.2.0",
|
"@lingui/detect-locale": "^5.2.0",
|
||||||
@ -41,7 +40,7 @@
|
|||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"colord": "^2.9.3",
|
"colord": "^2.9.3",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"hono": "4.6.15",
|
"hono": "4.7.0",
|
||||||
"hono-react-router-adapter": "^0.6.2",
|
"hono-react-router-adapter": "^0.6.2",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"isbot": "^5.1.17",
|
"isbot": "^5.1.17",
|
||||||
@ -98,6 +97,7 @@
|
|||||||
"typescript": "5.6.2",
|
"typescript": "5.6.2",
|
||||||
"vite": "^6.1.0",
|
"vite": "^6.1.0",
|
||||||
"vite-plugin-babel-macros": "^1.0.6",
|
"vite-plugin-babel-macros": "^1.0.6",
|
||||||
|
"vite-plugin-checker": "^0.8.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import linguiMacro from '@lingui/babel-plugin-lingui-macro';
|
||||||
import babel from '@rollup/plugin-babel';
|
import babel from '@rollup/plugin-babel';
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
@ -21,7 +22,7 @@ const config = {
|
|||||||
external: [/node_modules/],
|
external: [/node_modules/],
|
||||||
plugins: [
|
plugins: [
|
||||||
typescript({
|
typescript({
|
||||||
// noEmitOnError: true,
|
noEmitOnError: true,
|
||||||
moduleResolution: 'bundler',
|
moduleResolution: 'bundler',
|
||||||
include: ['server/**/*', '../../packages/**/*', '../../packages/lib/translations/**/*'],
|
include: ['server/**/*', '../../packages/**/*', '../../packages/lib/translations/**/*'],
|
||||||
}),
|
}),
|
||||||
@ -33,6 +34,7 @@ const config = {
|
|||||||
'@documenso/auth/*',
|
'@documenso/auth/*',
|
||||||
'@documenso/lib/*',
|
'@documenso/lib/*',
|
||||||
'@documenso/trpc/*',
|
'@documenso/trpc/*',
|
||||||
|
'@documenso/email/*',
|
||||||
],
|
],
|
||||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
|
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
|
||||||
}),
|
}),
|
||||||
@ -41,7 +43,7 @@ const config = {
|
|||||||
babelHelpers: 'bundled',
|
babelHelpers: 'bundled',
|
||||||
extensions: ['.js', '.ts', '.tsx'],
|
extensions: ['.js', '.ts', '.tsx'],
|
||||||
presets: ['@babel/preset-typescript'],
|
presets: ['@babel/preset-typescript'],
|
||||||
plugins: ['@lingui/babel-plugin-lingui-macro'],
|
plugins: [linguiMacro],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
110
apps/remix/server/api/files.ts
Normal file
110
apps/remix/server/api/files.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { sValidator } from '@hono/standard-validator';
|
||||||
|
import { Hono } from 'hono';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
|
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||||
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
||||||
|
import { putFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||||
|
import {
|
||||||
|
getPresignGetUrl,
|
||||||
|
getPresignPostUrl,
|
||||||
|
} from '@documenso/lib/universal/upload/server-actions';
|
||||||
|
|
||||||
|
import type { HonoEnv } from '../router';
|
||||||
|
import {
|
||||||
|
type TGetPresignedGetUrlResponse,
|
||||||
|
type TGetPresignedPostUrlResponse,
|
||||||
|
ZGetPresignedGetUrlRequestSchema,
|
||||||
|
ZGetPresignedPostUrlRequestSchema,
|
||||||
|
ZUploadPdfRequestSchema,
|
||||||
|
} from './files.types';
|
||||||
|
|
||||||
|
export const filesRoute = new Hono<HonoEnv>()
|
||||||
|
/**
|
||||||
|
* Uploads a document file to the appropriate storage location and creates
|
||||||
|
* a document data record.
|
||||||
|
*/
|
||||||
|
.post('/upload-pdf', sValidator('form', ZUploadPdfRequestSchema), async (c) => {
|
||||||
|
try {
|
||||||
|
const { file } = c.req.valid('form');
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return c.json({ error: 'No file provided' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo: Do we want to validate the file type?
|
||||||
|
// if (file.type !== 'application/pdf') {
|
||||||
|
// return c.json({ error: 'File must be a PDF' }, 400);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Todo: This is new.
|
||||||
|
// Add file size validation.
|
||||||
|
// Convert MB to bytes (1 MB = 1024 * 1024 bytes)
|
||||||
|
const MAX_FILE_SIZE = APP_DOCUMENT_UPLOAD_SIZE_LIMIT * 1024 * 1024;
|
||||||
|
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
return c.json({ error: 'File too large' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
|
||||||
|
const pdf = await PDFDocument.load(arrayBuffer).catch((e) => {
|
||||||
|
console.error(`PDF upload parse error: ${e.message}`);
|
||||||
|
|
||||||
|
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pdf.isEncrypted) {
|
||||||
|
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo: Test this.
|
||||||
|
if (!file.name.endsWith('.pdf')) {
|
||||||
|
Object.defineProperty(file, 'name', {
|
||||||
|
writable: true,
|
||||||
|
value: `${file.name}.pdf`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, data } = await putFileServerSide(file);
|
||||||
|
|
||||||
|
const result = await createDocumentData({ type, data });
|
||||||
|
|
||||||
|
return c.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload failed:', error);
|
||||||
|
return c.json({ error: 'Upload failed' }, 500);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post('/presigned-get-url', sValidator('json', ZGetPresignedGetUrlRequestSchema), async (c) => {
|
||||||
|
const { key } = await c.req.json();
|
||||||
|
console.log(key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { url } = await getPresignGetUrl(key || '');
|
||||||
|
|
||||||
|
return c.json({ url } satisfies TGetPresignedGetUrlResponse);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post('/presigned-post-url', sValidator('json', ZGetPresignedPostUrlRequestSchema), async (c) => {
|
||||||
|
const { fileName, contentType } = c.req.valid('json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log({
|
||||||
|
fileName,
|
||||||
|
});
|
||||||
|
const { key, url } = await getPresignPostUrl(fileName, contentType);
|
||||||
|
console.log(key);
|
||||||
|
|
||||||
|
return c.json({ key, url } satisfies TGetPresignedPostUrlResponse);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
throw new AppError(AppErrorCode.UNKNOWN_ERROR);
|
||||||
|
}
|
||||||
|
});
|
||||||
38
apps/remix/server/api/files.types.ts
Normal file
38
apps/remix/server/api/files.types.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||||
|
|
||||||
|
export const ZUploadPdfRequestSchema = z.object({
|
||||||
|
file: z.instanceof(File),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZUploadPdfResponseSchema = DocumentDataSchema.pick({
|
||||||
|
type: true,
|
||||||
|
id: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUploadPdfRequest = z.infer<typeof ZUploadPdfRequestSchema>;
|
||||||
|
export type TUploadPdfResponse = z.infer<typeof ZUploadPdfResponseSchema>;
|
||||||
|
|
||||||
|
export const ZGetPresignedPostUrlRequestSchema = z.object({
|
||||||
|
fileName: z.string().min(1),
|
||||||
|
contentType: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZGetPresignedPostUrlResponseSchema = z.object({
|
||||||
|
key: z.string().min(1),
|
||||||
|
url: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZGetPresignedGetUrlRequestSchema = z.object({
|
||||||
|
key: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ZGetPresignedGetUrlResponseSchema = z.object({
|
||||||
|
url: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGetPresignedPostUrlRequest = z.infer<typeof ZGetPresignedPostUrlRequestSchema>;
|
||||||
|
export type TGetPresignedPostUrlResponse = z.infer<typeof ZGetPresignedPostUrlResponseSchema>;
|
||||||
|
export type TGetPresignedGetUrlRequest = z.infer<typeof ZGetPresignedGetUrlRequestSchema>;
|
||||||
|
export type TGetPresignedGetUrlResponse = z.infer<typeof ZGetPresignedGetUrlResponseSchema>;
|
||||||
@ -1,12 +1,20 @@
|
|||||||
import type { Context, Next } from 'hono';
|
import type { Context, Next } from 'hono';
|
||||||
import { deleteCookie, getCookie, setCookie } from 'hono/cookie';
|
import { getCookie } from 'hono/cookie';
|
||||||
|
|
||||||
import { TEAM_URL_ROOT_REGEX } from '@documenso/lib/constants/teams';
|
import { setCsrfCookie } from '@documenso/auth/server/lib/session/session-cookies';
|
||||||
import { AppLogger } from '@documenso/lib/utils/debugger';
|
import { AppLogger } from '@documenso/lib/utils/debugger';
|
||||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
|
||||||
|
|
||||||
const logger = new AppLogger('Middleware');
|
const logger = new AppLogger('Middleware');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware for initial page loads.
|
||||||
|
*
|
||||||
|
* You won't be able to easily handle sequential page loads because they will be
|
||||||
|
* called under `path.data`
|
||||||
|
*
|
||||||
|
* Example an initial page load would be `/documents` then if the user click templates
|
||||||
|
* the path here would be `/templates.data`.
|
||||||
|
*/
|
||||||
export const appMiddleware = async (c: Context, next: Next) => {
|
export const appMiddleware = async (c: Context, next: Next) => {
|
||||||
const { req } = c;
|
const { req } = c;
|
||||||
const { path } = req;
|
const { path } = req;
|
||||||
@ -24,70 +32,64 @@ export const appMiddleware = async (c: Context, next: Next) => {
|
|||||||
const referrerUrl = referrer ? new URL(referrer) : null;
|
const referrerUrl = referrer ? new URL(referrer) : null;
|
||||||
const referrerPathname = referrerUrl ? referrerUrl.pathname : null;
|
const referrerPathname = referrerUrl ? referrerUrl.pathname : null;
|
||||||
|
|
||||||
// Whether to reset the preferred team url cookie if the user accesses a non team page from a team page.
|
// Set csrf token if not set.
|
||||||
const resetPreferredTeamUrl =
|
const csrfToken = getCookie(c, 'csrfToken');
|
||||||
referrerPathname &&
|
|
||||||
referrerPathname.startsWith('/t/') &&
|
|
||||||
(!path.startsWith('/t/') || path === '/');
|
|
||||||
|
|
||||||
// Redirect root page to `/documents` or `/t/{preferredTeamUrl}/documents`.
|
// Todo: Currently not working.
|
||||||
if (path === '/') {
|
if (!csrfToken) {
|
||||||
logger.log('Redirecting from root to documents');
|
await setCsrfCookie(c);
|
||||||
|
|
||||||
const redirectUrlPath = formatDocumentsPath(
|
|
||||||
resetPreferredTeamUrl ? undefined : preferredTeamUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
const redirectUrl = new URL(redirectUrlPath, req.url);
|
|
||||||
|
|
||||||
return c.redirect(redirectUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect `/t` to `/settings/teams`.
|
// // Whether to reset the preferred team url cookie if the user accesses a non team page from a team page.
|
||||||
if (path === '/t' || path === '/t/') {
|
// const resetPreferredTeamUrl =
|
||||||
logger.log('Redirecting to /settings/teams');
|
// referrerPathname &&
|
||||||
|
// referrerPathname.startsWith('/t/') &&
|
||||||
|
// (!path.startsWith('/t/') || path === '/');
|
||||||
|
|
||||||
const redirectUrl = new URL('/settings/teams', req.url);
|
// // Redirect root page to `/documents` or `/t/{preferredTeamUrl}/documents`.
|
||||||
return c.redirect(redirectUrl);
|
// if (path === '/') {
|
||||||
}
|
// logger.log('Redirecting from root to documents');
|
||||||
|
|
||||||
// Redirect `/t/<team_url>` to `/t/<team_url>/documents`.
|
// const redirectUrlPath = formatDocumentsPath(
|
||||||
if (TEAM_URL_ROOT_REGEX.test(path)) {
|
// resetPreferredTeamUrl ? undefined : preferredTeamUrl,
|
||||||
logger.log('Redirecting team documents');
|
// );
|
||||||
|
|
||||||
const redirectUrl = new URL(`${path}/documents`, req.url);
|
// const redirectUrl = new URL(redirectUrlPath, req.url);
|
||||||
setCookie(c, 'preferred-team-url', path.replace('/t/', ''));
|
|
||||||
|
|
||||||
return c.redirect(redirectUrl);
|
// return c.redirect(redirectUrl);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Set the preferred team url cookie if user accesses a team page.
|
// // Redirect `/t` to `/settings/teams`.
|
||||||
if (path.startsWith('/t/')) {
|
// if (path === '/t' || path === '/t/') {
|
||||||
setCookie(c, 'preferred-team-url', path.split('/')[2]);
|
// logger.log('Redirecting to /settings/teams');
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear preferred team url cookie if user accesses a non team page from a team page.
|
// const redirectUrl = new URL('/settings/teams', req.url);
|
||||||
if (resetPreferredTeamUrl || path === '/documents') {
|
// return c.redirect(redirectUrl);
|
||||||
logger.log('Resetting preferred team url');
|
// }
|
||||||
|
|
||||||
deleteCookie(c, 'preferred-team-url');
|
// // Redirect `/t/<team_url>` to `/t/<team_url>/documents`.
|
||||||
return next();
|
// if (TEAM_URL_ROOT_REGEX.test(path)) {
|
||||||
}
|
// logger.log('Redirecting team documents');
|
||||||
|
|
||||||
// Todo: Test
|
// const redirectUrl = new URL(`${path}/documents`, req.url);
|
||||||
if (path.startsWith('/embed')) {
|
// setCookie(c, 'preferred-team-url', path.replace('/t/', ''));
|
||||||
const origin = req.header('Origin') ?? '*';
|
|
||||||
|
|
||||||
// Allow third parties to iframe the document.
|
// return c.redirect(redirectUrl);
|
||||||
c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
// }
|
||||||
c.header('Access-Control-Allow-Origin', origin);
|
|
||||||
c.header('Content-Security-Policy', `frame-ancestors ${origin}`);
|
|
||||||
c.header('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
||||||
c.header('X-Content-Type-Options', 'nosniff');
|
|
||||||
|
|
||||||
return next();
|
// // Set the preferred team url cookie if user accesses a team page.
|
||||||
}
|
// if (path.startsWith('/t/')) {
|
||||||
|
// setCookie(c, 'preferred-team-url', path.split('/')[2]);
|
||||||
|
// return next();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Clear preferred team url cookie if user accesses a non team page from a team page.
|
||||||
|
// if (resetPreferredTeamUrl || path === '/documents') {
|
||||||
|
// logger.log('Resetting preferred team url');
|
||||||
|
|
||||||
|
// deleteCookie(c, 'preferred-team-url');
|
||||||
|
// return next();
|
||||||
|
// }
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,18 +1,15 @@
|
|||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { contextStorage } from 'hono/context-storage';
|
import { contextStorage } from 'hono/context-storage';
|
||||||
import { PDFDocument } from 'pdf-lib';
|
|
||||||
|
|
||||||
import { tsRestHonoApp } from '@documenso/api/hono';
|
import { tsRestHonoApp } from '@documenso/api/hono';
|
||||||
import { auth } from '@documenso/auth/server';
|
import { auth } from '@documenso/auth/server';
|
||||||
import { API_V2_BETA_URL } from '@documenso/lib/constants/app';
|
import { API_V2_BETA_URL } from '@documenso/lib/constants/app';
|
||||||
import { AppError } from '@documenso/lib/errors/app-error';
|
|
||||||
import { jobsClient } from '@documenso/lib/jobs/client';
|
import { jobsClient } from '@documenso/lib/jobs/client';
|
||||||
import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data';
|
|
||||||
import { putFile } from '@documenso/lib/universal/upload/put-file';
|
|
||||||
import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions';
|
|
||||||
import { openApiDocument } from '@documenso/trpc/server/open-api';
|
import { openApiDocument } from '@documenso/trpc/server/open-api';
|
||||||
|
|
||||||
|
import { filesRoute } from './api/files';
|
||||||
import { type AppContext, appContext } from './context';
|
import { type AppContext, appContext } from './context';
|
||||||
|
import { appMiddleware } from './middleware';
|
||||||
import { openApiTrpcServerHandler } from './trpc/hono-trpc-open-api';
|
import { openApiTrpcServerHandler } from './trpc/hono-trpc-open-api';
|
||||||
import { reactRouterTrpcServer } from './trpc/hono-trpc-remix';
|
import { reactRouterTrpcServer } from './trpc/hono-trpc-remix';
|
||||||
|
|
||||||
@ -30,12 +27,17 @@ const app = new Hono<HonoEnv>();
|
|||||||
app.use(contextStorage());
|
app.use(contextStorage());
|
||||||
app.use(appContext);
|
app.use(appContext);
|
||||||
|
|
||||||
// App middleware.
|
/**
|
||||||
// app.use('*', appMiddleware);
|
* Middleware for initial page loads.
|
||||||
|
*/
|
||||||
|
app.use('*', appMiddleware);
|
||||||
|
|
||||||
// Auth server.
|
// Auth server.
|
||||||
app.route('/api/auth', auth);
|
app.route('/api/auth', auth);
|
||||||
|
|
||||||
|
// Files route.
|
||||||
|
app.route('/api/files', filesRoute);
|
||||||
|
|
||||||
// API servers. Todo: Configure max durations, etc?
|
// API servers. Todo: Configure max durations, etc?
|
||||||
app.route('/api/v1', tsRestHonoApp);
|
app.route('/api/v1', tsRestHonoApp);
|
||||||
app.use('/api/jobs/*', jobsClient.getApiHandler());
|
app.use('/api/jobs/*', jobsClient.getApiHandler());
|
||||||
@ -45,73 +47,4 @@ app.use('/api/trpc/*', reactRouterTrpcServer);
|
|||||||
app.get(`${API_V2_BETA_URL}/openapi.json`, (c) => c.json(openApiDocument));
|
app.get(`${API_V2_BETA_URL}/openapi.json`, (c) => c.json(openApiDocument));
|
||||||
app.use(`${API_V2_BETA_URL}/*`, async (c) => openApiTrpcServerHandler(c)); // Todo: Add next()?
|
app.use(`${API_V2_BETA_URL}/*`, async (c) => openApiTrpcServerHandler(c)); // Todo: Add next()?
|
||||||
|
|
||||||
// Temp uploader.
|
|
||||||
app
|
|
||||||
.post('/api/file', async (c) => {
|
|
||||||
try {
|
|
||||||
const formData = await c.req.formData();
|
|
||||||
const file = formData.get('file') as File;
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
return c.json({ error: 'No file provided' }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add file size validation
|
|
||||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
||||||
if (file.size > MAX_FILE_SIZE) {
|
|
||||||
return c.json({ error: 'File too large' }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
|
||||||
|
|
||||||
const pdf = await PDFDocument.load(arrayBuffer).catch((e) => {
|
|
||||||
console.error(`PDF upload parse error: ${e.message}`);
|
|
||||||
|
|
||||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pdf.isEncrypted) {
|
|
||||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo: Test this.
|
|
||||||
if (!file.name.endsWith('.pdf')) {
|
|
||||||
Object.defineProperty(file, 'name', {
|
|
||||||
writable: true,
|
|
||||||
value: `${file.name}.pdf`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { type, data } = await putFile(file);
|
|
||||||
|
|
||||||
const result = await createDocumentData({ type, data });
|
|
||||||
|
|
||||||
return c.json(result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Upload failed:', error);
|
|
||||||
return c.json({ error: 'Upload failed' }, 500);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.get('/api/file', async (c) => {
|
|
||||||
const key = c.req.query('key');
|
|
||||||
|
|
||||||
const { url } = await getPresignGetUrl(key || '');
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to get file "${key}", failed with status code ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = await response.arrayBuffer();
|
|
||||||
|
|
||||||
const binaryData = new Uint8Array(buffer);
|
|
||||||
|
|
||||||
return c.json({
|
|
||||||
binaryData,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true
|
"strict": true,
|
||||||
|
"useUnknownInCatchVariables": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
284
package-lock.json
generated
284
package-lock.json
generated
@ -108,7 +108,6 @@
|
|||||||
"@epic-web/remember": "^1.1.0",
|
"@epic-web/remember": "^1.1.0",
|
||||||
"@hono/node-server": "^1.13.7",
|
"@hono/node-server": "^1.13.7",
|
||||||
"@hono/trpc-server": "^0.3.4",
|
"@hono/trpc-server": "^0.3.4",
|
||||||
"@hono/zod-validator": "^0.4.2",
|
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.1.0",
|
||||||
"@lingui/core": "^5.2.0",
|
"@lingui/core": "^5.2.0",
|
||||||
"@lingui/detect-locale": "^5.2.0",
|
"@lingui/detect-locale": "^5.2.0",
|
||||||
@ -123,7 +122,7 @@
|
|||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"colord": "^2.9.3",
|
"colord": "^2.9.3",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"hono": "4.6.15",
|
"hono": "4.7.0",
|
||||||
"hono-react-router-adapter": "^0.6.2",
|
"hono-react-router-adapter": "^0.6.2",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"isbot": "^5.1.17",
|
"isbot": "^5.1.17",
|
||||||
@ -180,6 +179,7 @@
|
|||||||
"typescript": "5.6.2",
|
"typescript": "5.6.2",
|
||||||
"vite": "^6.1.0",
|
"vite": "^6.1.0",
|
||||||
"vite-plugin-babel-macros": "^1.0.6",
|
"vite-plugin-babel-macros": "^1.0.6",
|
||||||
|
"vite-plugin-checker": "^0.8.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -849,6 +849,16 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"apps/remix/node_modules/commander": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"apps/remix/node_modules/esbuild": {
|
"apps/remix/node_modules/esbuild": {
|
||||||
"version": "0.24.2",
|
"version": "0.24.2",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
|
||||||
@ -890,6 +900,35 @@
|
|||||||
"@esbuild/win32-x64": "0.24.2"
|
"@esbuild/win32-x64": "0.24.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"apps/remix/node_modules/meow": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/minimist": "^1.2.0",
|
||||||
|
"camelcase-keys": "^6.2.2",
|
||||||
|
"decamelize": "^1.2.0",
|
||||||
|
"decamelize-keys": "^1.1.0",
|
||||||
|
"hard-rejection": "^2.1.0",
|
||||||
|
"minimist-options": "4.1.0",
|
||||||
|
"normalize-package-data": "^3.0.0",
|
||||||
|
"read-pkg-up": "^7.0.1",
|
||||||
|
"redent": "^3.0.0",
|
||||||
|
"trim-newlines": "^3.0.0",
|
||||||
|
"type-fest": "^0.18.0",
|
||||||
|
"yargs-parser": "^20.2.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"apps/remix/node_modules/rollup": {
|
"apps/remix/node_modules/rollup": {
|
||||||
"version": "4.34.5",
|
"version": "4.34.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.5.tgz",
|
||||||
@ -929,6 +968,88 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"apps/remix/node_modules/type-fest": {
|
||||||
|
"version": "0.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
|
||||||
|
"integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps/remix/node_modules/vite-plugin-checker": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-UA5uzOGm97UvZRTdZHiQVYFnd86AVn8EVaD4L3PoVzxH+IZSfaAw14WGFwX9QS23UW3lV/5bVKZn6l0w+q9P0g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.12.13",
|
||||||
|
"ansi-escapes": "^4.3.0",
|
||||||
|
"chalk": "^4.1.1",
|
||||||
|
"chokidar": "^3.5.1",
|
||||||
|
"commander": "^8.0.0",
|
||||||
|
"fast-glob": "^3.2.7",
|
||||||
|
"fs-extra": "^11.1.0",
|
||||||
|
"npm-run-path": "^4.0.1",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"tiny-invariant": "^1.1.0",
|
||||||
|
"vscode-languageclient": "^7.0.0",
|
||||||
|
"vscode-languageserver": "^7.0.0",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.1",
|
||||||
|
"vscode-uri": "^3.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@biomejs/biome": ">=1.7",
|
||||||
|
"eslint": ">=7",
|
||||||
|
"meow": "^9.0.0",
|
||||||
|
"optionator": "^0.9.1",
|
||||||
|
"stylelint": ">=13",
|
||||||
|
"typescript": "*",
|
||||||
|
"vite": ">=2.0.0",
|
||||||
|
"vls": "*",
|
||||||
|
"vti": "*",
|
||||||
|
"vue-tsc": "~2.1.6"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@biomejs/biome": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"eslint": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"meow": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"optionator": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"stylelint": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vls": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vti": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vue-tsc": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
@ -1024,7 +1145,7 @@
|
|||||||
"@auth/core": "0.28.2"
|
"@auth/core": "0.28.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"kysely": "^0.26.1"
|
"kysely": "^0.26.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aws-crypto/crc32": {
|
"node_modules/@aws-crypto/crc32": {
|
||||||
@ -3801,6 +3922,16 @@
|
|||||||
"hono": "^4"
|
"hono": "^4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hono/standard-validator": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hono/standard-validator/-/standard-validator-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-mVyv2fpx/o0MNAEhjXhvuVbW3BWTGnf8F4w8ZifztE+TWXjUAKr7KAOZfcDhVrurgVhKw7RbTnEog2beZM6QtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@standard-schema/spec": "1.0.0",
|
||||||
|
"hono": ">=3.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@hono/trpc-server": {
|
"node_modules/@hono/trpc-server": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@hono/trpc-server/-/trpc-server-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@hono/trpc-server/-/trpc-server-0.3.4.tgz",
|
||||||
@ -3867,16 +3998,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@hono/zod-validator": {
|
|
||||||
"version": "0.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.4.2.tgz",
|
|
||||||
"integrity": "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"hono": ">=3.9.0",
|
|
||||||
"zod": "^3.19.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@hookform/resolvers": {
|
"node_modules/@hookform/resolvers": {
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
||||||
@ -10688,6 +10809,13 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/@swagger-api/apidom-ast": {
|
"node_modules/@swagger-api/apidom-ast": {
|
||||||
"version": "1.0.0-beta.11",
|
"version": "1.0.0-beta.11",
|
||||||
"resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.11.tgz",
|
"resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.11.tgz",
|
||||||
@ -22435,9 +22563,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/hono": {
|
"node_modules/hono": {
|
||||||
"version": "4.6.15",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.6.15.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.7.0.tgz",
|
||||||
"integrity": "sha512-OiQwvAOAaI2JrABBH69z5rsctHDzFzIKJge0nYXgtzGJ0KftwLWcBXm1upJC23/omNRtnqM0gjRMbtXshPdqhQ==",
|
"integrity": "sha512-hV97aIR4WYbG30k234sD9B3VNr1ZWdQRmrVF76LKFlmI7O9Yo70mG9+mFwyQ6Sjrz4wH71GfnBxv6CPjcx3QNw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
@ -39965,6 +40093,69 @@
|
|||||||
"@esbuild/win32-x64": "0.24.2"
|
"@esbuild/win32-x64": "0.24.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vscode-jsonrpc": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0 || >=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageclient": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minimatch": "^3.0.4",
|
||||||
|
"semver": "^7.3.4",
|
||||||
|
"vscode-languageserver-protocol": "3.16.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.52.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageserver-protocol": "3.16.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"installServerIntoExtension": "bin/installServerIntoExtension"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-protocol": {
|
||||||
|
"version": "3.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz",
|
||||||
|
"integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-jsonrpc": "6.0.0",
|
||||||
|
"vscode-languageserver-types": "3.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-textdocument": {
|
||||||
|
"version": "1.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
|
||||||
|
"integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-types": {
|
||||||
|
"version": "3.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
|
||||||
|
"integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/vscode-oniguruma": {
|
"node_modules/vscode-oniguruma": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
|
||||||
@ -39977,6 +40168,13 @@
|
|||||||
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==",
|
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vscode-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wait-on": {
|
"node_modules/wait-on": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.2.tgz",
|
||||||
@ -40754,11 +40952,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/lib": "*",
|
"@documenso/lib": "*",
|
||||||
"@documenso/prisma": "*",
|
"@documenso/prisma": "*",
|
||||||
"@hono/zod-validator": "^0.4.2",
|
"@hono/standard-validator": "^0.1.2",
|
||||||
"@oslojs/crypto": "^1.0.1",
|
"@oslojs/crypto": "^1.0.1",
|
||||||
"@oslojs/encoding": "^1.1.0",
|
"@oslojs/encoding": "^1.1.0",
|
||||||
"arctic": "^3.1.0",
|
"arctic": "^3.1.0",
|
||||||
"hono": "4.6.15",
|
"hono": "4.7.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
@ -41706,7 +41904,7 @@
|
|||||||
"@upstash/redis": "^1.20.6",
|
"@upstash/redis": "^1.20.6",
|
||||||
"@vvo/tzdb": "^6.117.0",
|
"@vvo/tzdb": "^6.117.0",
|
||||||
"inngest": "^3.19.13",
|
"inngest": "^3.19.13",
|
||||||
"kysely": "^0.26.3",
|
"kysely": "0.26.3",
|
||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
@ -41744,7 +41942,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^5.4.2",
|
"@prisma/client": "^5.4.2",
|
||||||
"kysely": "^0.27.3",
|
"kysely": "0.26.3",
|
||||||
"prisma": "^5.4.2",
|
"prisma": "^5.4.2",
|
||||||
"prisma-extension-kysely": "^2.1.0",
|
"prisma-extension-kysely": "^2.1.0",
|
||||||
"ts-pattern": "^5.0.6"
|
"ts-pattern": "^5.0.6"
|
||||||
@ -41759,15 +41957,6 @@
|
|||||||
"zod-prisma-types": "3.1.9"
|
"zod-prisma-types": "3.1.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/prisma/node_modules/kysely": {
|
|
||||||
"version": "0.27.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.27.5.tgz",
|
|
||||||
"integrity": "sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/signing": {
|
"packages/signing": {
|
||||||
"name": "@documenso/signing",
|
"name": "@documenso/signing",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
@ -42253,24 +42442,6 @@
|
|||||||
"zod": "3.24.1"
|
"zod": "3.24.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/trpc/node_modules/@ts-rest/core": {
|
|
||||||
"version": "3.51.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ts-rest/core/-/core-3.51.0.tgz",
|
|
||||||
"integrity": "sha512-v6lnWEcpZj1UgN9wb84XQ+EORP1QEtncFumoXMJjno5ZUV6vdjKze3MYcQN0C6vjBpIJPQEaI/gab2jr4/0KzQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/node": "^18.18.7 || >=20.8.4",
|
|
||||||
"zod": "^3.22.3"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/node": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"zod": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/trpc/node_modules/@ts-rest/next": {
|
"packages/trpc/node_modules/@ts-rest/next": {
|
||||||
"version": "3.51.0",
|
"version": "3.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ts-rest/next/-/next-3.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ts-rest/next/-/next-3.51.0.tgz",
|
||||||
@ -42287,25 +42458,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/trpc/node_modules/@types/node": {
|
|
||||||
"version": "22.13.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
|
|
||||||
"integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~6.20.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/trpc/node_modules/undici-types": {
|
|
||||||
"version": "6.20.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
|
||||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"packages/tsconfig": {
|
"packages/tsconfig": {
|
||||||
"name": "@documenso/tsconfig",
|
"name": "@documenso/tsconfig",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
|||||||
@ -42,8 +42,8 @@ import {
|
|||||||
ZRadioFieldMeta,
|
ZRadioFieldMeta,
|
||||||
ZTextFieldMeta,
|
ZTextFieldMeta,
|
||||||
} from '@documenso/lib/types/field-meta';
|
} from '@documenso/lib/types/field-meta';
|
||||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||||
import {
|
import {
|
||||||
getPresignGetUrl,
|
getPresignGetUrl,
|
||||||
getPresignPostUrl,
|
getPresignPostUrl,
|
||||||
@ -490,14 +490,14 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
|||||||
let documentDataId = document.documentDataId;
|
let documentDataId = document.documentDataId;
|
||||||
|
|
||||||
if (body.formValues) {
|
if (body.formValues) {
|
||||||
const pdf = await getFile(document.documentData);
|
const pdf = await getFileServerSide(document.documentData);
|
||||||
|
|
||||||
const prefilled = await insertFormValuesInPdf({
|
const prefilled = await insertFormValuesInPdf({
|
||||||
pdf: Buffer.from(pdf),
|
pdf: Buffer.from(pdf),
|
||||||
formValues: body.formValues,
|
formValues: body.formValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newDocumentData = await putPdfFile({
|
const newDocumentData = await putPdfFileServerSide({
|
||||||
name: fileName,
|
name: fileName,
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||||
@ -598,14 +598,14 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
|||||||
if (body.formValues) {
|
if (body.formValues) {
|
||||||
const fileName = document.title.endsWith('.pdf') ? document.title : `${document.title}.pdf`;
|
const fileName = document.title.endsWith('.pdf') ? document.title : `${document.title}.pdf`;
|
||||||
|
|
||||||
const pdf = await getFile(document.documentData);
|
const pdf = await getFileServerSide(document.documentData);
|
||||||
|
|
||||||
const prefilled = await insertFormValuesInPdf({
|
const prefilled = await insertFormValuesInPdf({
|
||||||
pdf: Buffer.from(pdf),
|
pdf: Buffer.from(pdf),
|
||||||
formValues: body.formValues,
|
formValues: body.formValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newDocumentData = await putPdfFile({
|
const newDocumentData = await putPdfFileServerSide({
|
||||||
name: fileName,
|
name: fileName,
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||||
|
|||||||
@ -12,11 +12,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/lib": "*",
|
"@documenso/lib": "*",
|
||||||
"@documenso/prisma": "*",
|
"@documenso/prisma": "*",
|
||||||
"@hono/zod-validator": "^0.4.2",
|
"@hono/standard-validator": "^0.1.2",
|
||||||
"@oslojs/crypto": "^1.0.1",
|
"@oslojs/crypto": "^1.0.1",
|
||||||
"@oslojs/encoding": "^1.1.0",
|
"@oslojs/encoding": "^1.1.0",
|
||||||
"arctic": "^3.1.0",
|
"arctic": "^3.1.0",
|
||||||
"hono": "4.6.15",
|
"hono": "4.7.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
|
|||||||
@ -5,14 +5,28 @@ import { type Session, type User, UserSecurityAuditLogType } from '@prisma/clien
|
|||||||
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user object to pass around the app.
|
||||||
|
*
|
||||||
|
* Do not put anything sensitive in here since it will be public.
|
||||||
|
*/
|
||||||
|
export type SessionUser = Pick<
|
||||||
|
User,
|
||||||
|
| 'id'
|
||||||
|
| 'name'
|
||||||
|
| 'email'
|
||||||
|
| 'emailVerified'
|
||||||
|
| 'avatarImageId'
|
||||||
|
| 'twoFactorEnabled'
|
||||||
|
| 'roles'
|
||||||
|
| 'signature'
|
||||||
|
| 'url'
|
||||||
|
>;
|
||||||
|
|
||||||
export type SessionValidationResult =
|
export type SessionValidationResult =
|
||||||
| {
|
| {
|
||||||
session: Session;
|
session: Session;
|
||||||
user: User;
|
user: SessionUser;
|
||||||
// user: Pick<
|
|
||||||
// User,
|
|
||||||
// 'id' | 'name' | 'email' | 'emailVerified' | 'avatarImageId' | 'twoFactorEnabled' | 'roles' // Todo
|
|
||||||
// >;
|
|
||||||
isAuthenticated: true;
|
isAuthenticated: true;
|
||||||
}
|
}
|
||||||
| { session: null; user: null; isAuthenticated: false };
|
| { session: null; user: null; isAuthenticated: false };
|
||||||
@ -36,7 +50,7 @@ export const createSession = async (
|
|||||||
|
|
||||||
const session: Session = {
|
const session: Session = {
|
||||||
id: hashedSessionId,
|
id: hashedSessionId,
|
||||||
sessionToken: hashedSessionId, // todo
|
sessionToken: hashedSessionId,
|
||||||
userId,
|
userId,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@ -69,23 +83,26 @@ export const validateSessionToken = async (token: string): Promise<SessionValida
|
|||||||
id: sessionId,
|
id: sessionId,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: {
|
||||||
|
/**
|
||||||
|
* Do not expose anything sensitive here.
|
||||||
|
*/
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
emailVerified: true,
|
||||||
|
avatarImageId: true,
|
||||||
|
twoFactorEnabled: true,
|
||||||
|
roles: true,
|
||||||
|
signature: true,
|
||||||
|
url: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// user: {
|
if (!result?.user) {
|
||||||
// select: {
|
|
||||||
// id: true,
|
|
||||||
// name: true,
|
|
||||||
// email: true,
|
|
||||||
// emailVerified: true,
|
|
||||||
// avatarImageId: true,
|
|
||||||
// twoFactorEnabled: true,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
|
|
||||||
// todo; how can result.user be null?
|
|
||||||
if (result === null || !result.user) {
|
|
||||||
return { session: null, user: null, isAuthenticated: false };
|
return { session: null, user: null, isAuthenticated: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { zValidator } from '@hono/zod-validator';
|
import { sValidator } from '@hono/standard-validator';
|
||||||
import { compare } from '@node-rs/bcrypt';
|
import { compare } from '@node-rs/bcrypt';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@ -42,7 +42,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Authorize endpoint.
|
* Authorize endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/authorize', zValidator('json', ZSignInSchema), async (c) => {
|
.post('/authorize', sValidator('json', ZSignInSchema), async (c) => {
|
||||||
const requestMetadata = c.get('requestMetadata');
|
const requestMetadata = c.get('requestMetadata');
|
||||||
|
|
||||||
const { email, password, totpCode, backupCode } = c.req.valid('json');
|
const { email, password, totpCode, backupCode } = c.req.valid('json');
|
||||||
@ -131,7 +131,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Signup endpoint.
|
* Signup endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/signup', zValidator('json', ZSignUpSchema), async (c) => {
|
.post('/signup', sValidator('json', ZSignUpSchema), async (c) => {
|
||||||
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
if (env('NEXT_PUBLIC_DISABLE_SIGNUP') === 'true') {
|
||||||
throw new AppError('SIGNUP_DISABLED', {
|
throw new AppError('SIGNUP_DISABLED', {
|
||||||
message: 'Signups are disabled.',
|
message: 'Signups are disabled.',
|
||||||
@ -160,7 +160,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Update password endpoint.
|
* Update password endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/update-password', zValidator('json', ZUpdatePasswordSchema), async (c) => {
|
.post('/update-password', sValidator('json', ZUpdatePasswordSchema), async (c) => {
|
||||||
const { password, currentPassword } = c.req.valid('json');
|
const { password, currentPassword } = c.req.valid('json');
|
||||||
const requestMetadata = c.get('requestMetadata');
|
const requestMetadata = c.get('requestMetadata');
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Verify email endpoint.
|
* Verify email endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/verify-email', zValidator('json', ZVerifyEmailSchema), async (c) => {
|
.post('/verify-email', sValidator('json', ZVerifyEmailSchema), async (c) => {
|
||||||
const { state, userId } = await verifyEmail({ token: c.req.valid('json').token });
|
const { state, userId } = await verifyEmail({ token: c.req.valid('json').token });
|
||||||
|
|
||||||
// If email is verified, automatically authenticate user.
|
// If email is verified, automatically authenticate user.
|
||||||
@ -197,7 +197,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Resend verification email endpoint.
|
* Resend verification email endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/resend-verify-email', zValidator('json', ZResendVerifyEmailSchema), async (c) => {
|
.post('/resend-verify-email', sValidator('json', ZResendVerifyEmailSchema), async (c) => {
|
||||||
const { email } = c.req.valid('json');
|
const { email } = c.req.valid('json');
|
||||||
|
|
||||||
await jobsClient.triggerJob({
|
await jobsClient.triggerJob({
|
||||||
@ -212,7 +212,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Forgot password endpoint.
|
* Forgot password endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/forgot-password', zValidator('json', ZForgotPasswordSchema), async (c) => {
|
.post('/forgot-password', sValidator('json', ZForgotPasswordSchema), async (c) => {
|
||||||
const { email } = c.req.valid('json');
|
const { email } = c.req.valid('json');
|
||||||
|
|
||||||
await forgotPassword({
|
await forgotPassword({
|
||||||
@ -224,7 +224,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Reset password endpoint.
|
* Reset password endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/reset-password', zValidator('json', ZResetPasswordSchema), async (c) => {
|
.post('/reset-password', sValidator('json', ZResetPasswordSchema), async (c) => {
|
||||||
const { token, password } = c.req.valid('json');
|
const { token, password } = c.req.valid('json');
|
||||||
|
|
||||||
const requestMetadata = c.get('requestMetadata');
|
const requestMetadata = c.get('requestMetadata');
|
||||||
@ -258,7 +258,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
*/
|
*/
|
||||||
.post(
|
.post(
|
||||||
'/2fa/enable',
|
'/2fa/enable',
|
||||||
zValidator(
|
sValidator(
|
||||||
'json',
|
'json',
|
||||||
z.object({
|
z.object({
|
||||||
code: z.string(),
|
code: z.string(),
|
||||||
@ -304,7 +304,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
*/
|
*/
|
||||||
.post(
|
.post(
|
||||||
'/2fa/disable',
|
'/2fa/disable',
|
||||||
zValidator(
|
sValidator(
|
||||||
'json',
|
'json',
|
||||||
z.object({
|
z.object({
|
||||||
totpCode: z.string().trim().optional(),
|
totpCode: z.string().trim().optional(),
|
||||||
@ -325,6 +325,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
email: true,
|
email: true,
|
||||||
twoFactorEnabled: true,
|
twoFactorEnabled: true,
|
||||||
twoFactorSecret: true,
|
twoFactorSecret: true,
|
||||||
|
twoFactorBackupCodes: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -349,7 +350,7 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
*/
|
*/
|
||||||
.post(
|
.post(
|
||||||
'/2fa/view-recovery-codes',
|
'/2fa/view-recovery-codes',
|
||||||
zValidator(
|
sValidator(
|
||||||
'json',
|
'json',
|
||||||
z.object({
|
z.object({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { zValidator } from '@hono/zod-validator';
|
import { sValidator } from '@hono/standard-validator';
|
||||||
import { Google, decodeIdToken, generateCodeVerifier, generateState } from 'arctic';
|
import { Google, decodeIdToken, generateCodeVerifier, generateState } from 'arctic';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { deleteCookie, setCookie } from 'hono/cookie';
|
import { deleteCookie, setCookie } from 'hono/cookie';
|
||||||
@ -35,7 +35,7 @@ export const googleRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Authorize endpoint.
|
* Authorize endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/authorize', zValidator('json', ZGoogleAuthorizeSchema), (c) => {
|
.post('/authorize', sValidator('json', ZGoogleAuthorizeSchema), (c) => {
|
||||||
const scopes = options.scope;
|
const scopes = options.scope;
|
||||||
const state = generateState();
|
const state = generateState();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { zValidator } from '@hono/zod-validator';
|
import { sValidator } from '@hono/standard-validator';
|
||||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||||
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
|
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
@ -17,7 +17,7 @@ export const passkeyRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Authorize endpoint.
|
* Authorize endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/authorize', zValidator('json', ZPasskeyAuthorizeSchema), async (c) => {
|
.post('/authorize', sValidator('json', ZPasskeyAuthorizeSchema), async (c) => {
|
||||||
const requestMetadata = c.get('requestMetadata');
|
const requestMetadata = c.get('requestMetadata');
|
||||||
|
|
||||||
const { csrfToken, credential } = c.req.valid('json');
|
const { csrfToken, credential } = c.req.valid('json');
|
||||||
@ -129,7 +129,7 @@ export const passkeyRoute = new Hono<HonoAuthContext>()
|
|||||||
|
|
||||||
// .post(
|
// .post(
|
||||||
// '/pre-authenticate',
|
// '/pre-authenticate',
|
||||||
// zValidator(
|
// sValidator(
|
||||||
// 'json',
|
// 'json',
|
||||||
// z.object({
|
// z.object({
|
||||||
// code: z.string(),
|
// code: z.string(),
|
||||||
|
|||||||
1
packages/email/ambient.d.ts
vendored
1
packages/email/ambient.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
declare module '@documenso/tailwind-config';
|
|
||||||
@ -9,6 +9,9 @@ export type RenderOptions = ReactEmail.Options & {
|
|||||||
branding?: BrandingSettings;
|
branding?: BrandingSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const colors = (config.theme?.extend?.colors || {}) as Record<string, string>;
|
||||||
|
|
||||||
export const render = (element: React.ReactNode, options?: RenderOptions) => {
|
export const render = (element: React.ReactNode, options?: RenderOptions) => {
|
||||||
const { branding, ...otherOptions } = options ?? {};
|
const { branding, ...otherOptions } = options ?? {};
|
||||||
|
|
||||||
@ -17,7 +20,7 @@ export const render = (element: React.ReactNode, options?: RenderOptions) => {
|
|||||||
config={{
|
config={{
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: config.theme.extend.colors,
|
colors,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -36,7 +39,7 @@ export const renderAsync = async (element: React.ReactNode, options?: RenderOpti
|
|||||||
config={{
|
config={{
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: config.theme.extend.colors,
|
colors,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const DocumentInviteEmailTemplate = ({
|
|||||||
role,
|
role,
|
||||||
selfSigner = false,
|
selfSigner = false,
|
||||||
isTeamInvite = false,
|
isTeamInvite = false,
|
||||||
teamName,
|
teamName = '',
|
||||||
includeSenderDetails,
|
includeSenderDetails,
|
||||||
}: DocumentInviteEmailTemplateProps) => {
|
}: DocumentInviteEmailTemplateProps) => {
|
||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type { Session, User } from '@documenso/prisma/client';
|
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||||
|
import type { Session } from '@documenso/prisma/client';
|
||||||
|
|
||||||
import type { TGetTeamByUrlResponse } from '../../server-only/team/get-team';
|
import type { TGetTeamByUrlResponse } from '../../server-only/team/get-team';
|
||||||
import type { TGetTeamsResponse } from '../../server-only/team/get-teams';
|
import type { TGetTeamsResponse } from '../../server-only/team/get-teams';
|
||||||
|
|
||||||
export type AppSession = {
|
export type AppSession = {
|
||||||
session: Session;
|
session: Session;
|
||||||
user: User; // Todo: Remove password, and redundant fields.
|
user: SessionUser;
|
||||||
currentTeam: TGetTeamByUrlResponse | null;
|
currentTeam: TGetTeamByUrlResponse | null;
|
||||||
teams: TGetTeamsResponse;
|
teams: TGetTeamsResponse;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -114,9 +114,11 @@ export const run = async ({
|
|||||||
emailMessage = customEmail?.message ?? '';
|
emailMessage = customEmail?.message ?? '';
|
||||||
|
|
||||||
if (!emailMessage) {
|
if (!emailMessage) {
|
||||||
|
const inviterName = user.name || '';
|
||||||
|
|
||||||
emailMessage = i18n._(
|
emailMessage = i18n._(
|
||||||
team.teamGlobalSettings?.includeSenderDetails
|
team.teamGlobalSettings?.includeSenderDetails
|
||||||
? msg`${user.name} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
|
? msg`${inviterName} on behalf of "${team.name}" has invited you to ${recipientActionVerb} the document "${document.title}".`
|
||||||
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
: msg`${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,8 @@ import {
|
|||||||
ZWebhookDocumentSchema,
|
ZWebhookDocumentSchema,
|
||||||
mapDocumentToWebhookDocumentPayload,
|
mapDocumentToWebhookDocumentPayload,
|
||||||
} from '../../../types/webhook-payload';
|
} from '../../../types/webhook-payload';
|
||||||
import { getFile } from '../../../universal/upload/get-file';
|
import { getFileServerSide } from '../../../universal/upload/get-file.server';
|
||||||
import { putPdfFile } from '../../../universal/upload/put-file';
|
import { putPdfFileServerSide } from '../../../universal/upload/put-file.server';
|
||||||
import { fieldsContainUnsignedRequiredField } from '../../../utils/advanced-fields-helpers';
|
import { fieldsContainUnsignedRequiredField } from '../../../utils/advanced-fields-helpers';
|
||||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||||
import type { JobRunIO } from '../../client/_internal/job';
|
import type { JobRunIO } from '../../client/_internal/job';
|
||||||
@ -114,7 +114,7 @@ export const run = async ({
|
|||||||
documentData.data = documentData.initialData;
|
documentData.data = documentData.initialData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdfData = await getFile(documentData);
|
const pdfData = await getFileServerSide(documentData);
|
||||||
|
|
||||||
const certificateData =
|
const certificateData =
|
||||||
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||||
@ -160,7 +160,7 @@ export const run = async ({
|
|||||||
|
|
||||||
const { name } = path.parse(document.title);
|
const { name } = path.parse(document.title);
|
||||||
|
|
||||||
const documentData = await putPdfFile({
|
const documentData = await putPdfFileServerSide({
|
||||||
name: `${name}_signed.pdf`,
|
name: `${name}_signed.pdf`,
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
"@upstash/redis": "^1.20.6",
|
"@upstash/redis": "^1.20.6",
|
||||||
"@vvo/tzdb": "^6.117.0",
|
"@vvo/tzdb": "^6.117.0",
|
||||||
"inngest": "^3.19.13",
|
"inngest": "^3.19.13",
|
||||||
"kysely": "^0.26.3",
|
"kysely": "0.26.3",
|
||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
|
|||||||
@ -8,7 +8,10 @@ import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
|||||||
import { validateTwoFactorAuthentication } from './validate-2fa';
|
import { validateTwoFactorAuthentication } from './validate-2fa';
|
||||||
|
|
||||||
type DisableTwoFactorAuthenticationOptions = {
|
type DisableTwoFactorAuthenticationOptions = {
|
||||||
user: Pick<User, 'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret'>;
|
user: Pick<
|
||||||
|
User,
|
||||||
|
'id' | 'email' | 'twoFactorEnabled' | 'twoFactorSecret' | 'twoFactorBackupCodes'
|
||||||
|
>;
|
||||||
totpCode?: string;
|
totpCode?: string;
|
||||||
backupCode?: string;
|
backupCode?: string;
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata?: RequestMetadata;
|
||||||
|
|||||||
@ -24,8 +24,8 @@ import {
|
|||||||
ZWebhookDocumentSchema,
|
ZWebhookDocumentSchema,
|
||||||
mapDocumentToWebhookDocumentPayload,
|
mapDocumentToWebhookDocumentPayload,
|
||||||
} from '../../types/webhook-payload';
|
} from '../../types/webhook-payload';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||||
import { putPdfFile } from '../../universal/upload/put-file';
|
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||||
import { createDocumentAuthOptions, createRecipientAuthOptions } from '../../utils/document-auth';
|
import { createDocumentAuthOptions, createRecipientAuthOptions } from '../../utils/document-auth';
|
||||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
@ -97,11 +97,11 @@ export const createDocumentV2 = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (documentData) {
|
if (documentData) {
|
||||||
const buffer = await getFile(documentData);
|
const buffer = await getFileServerSide(documentData);
|
||||||
|
|
||||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||||
|
|
||||||
const newDocumentData = await putPdfFile({
|
const newDocumentData = await putPdfFileServerSide({
|
||||||
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
||||||
|
|||||||
@ -13,8 +13,8 @@ import {
|
|||||||
ZWebhookDocumentSchema,
|
ZWebhookDocumentSchema,
|
||||||
mapDocumentToWebhookDocumentPayload,
|
mapDocumentToWebhookDocumentPayload,
|
||||||
} from '../../types/webhook-payload';
|
} from '../../types/webhook-payload';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||||
import { putPdfFile } from '../../universal/upload/put-file';
|
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||||
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
import { determineDocumentVisibility } from '../../utils/document-visibility';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
|
|
||||||
@ -96,11 +96,11 @@ export const createDocument = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (documentData) {
|
if (documentData) {
|
||||||
const buffer = await getFile(documentData);
|
const buffer = await getFileServerSide(documentData);
|
||||||
|
|
||||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||||
|
|
||||||
const newDocumentData = await putPdfFile({
|
const newDocumentData = await putPdfFileServerSide({
|
||||||
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
name: title.endsWith('.pdf') ? title : `${title}.pdf`,
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
arrayBuffer: async () => Promise.resolve(normalizedPdf),
|
||||||
|
|||||||
@ -14,8 +14,8 @@ import {
|
|||||||
mapDocumentToWebhookDocumentPayload,
|
mapDocumentToWebhookDocumentPayload,
|
||||||
} from '../../types/webhook-payload';
|
} from '../../types/webhook-payload';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||||
import { putPdfFile } from '../../universal/upload/put-file';
|
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||||
import { fieldsContainUnsignedRequiredField } from '../../utils/advanced-fields-helpers';
|
import { fieldsContainUnsignedRequiredField } from '../../utils/advanced-fields-helpers';
|
||||||
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
|
import { getCertificatePdf } from '../htmltopdf/get-certificate-pdf';
|
||||||
import { flattenAnnotations } from '../pdf/flatten-annotations';
|
import { flattenAnnotations } from '../pdf/flatten-annotations';
|
||||||
@ -102,7 +102,7 @@ export const sealDocument = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// !: Need to write the fields onto the document as a hard copy
|
// !: Need to write the fields onto the document as a hard copy
|
||||||
const pdfData = await getFile(documentData);
|
const pdfData = await getFileServerSide(documentData);
|
||||||
|
|
||||||
const certificateData =
|
const certificateData =
|
||||||
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
(document.team?.teamGlobalSettings?.includeSigningCertificate ?? true)
|
||||||
@ -142,7 +142,7 @@ export const sealDocument = async ({
|
|||||||
|
|
||||||
const { name } = path.parse(document.title);
|
const { name } = path.parse(document.title);
|
||||||
|
|
||||||
const { data: newData } = await putPdfFile({
|
const { data: newData } = await putPdfFileServerSide({
|
||||||
name: `${name}_signed.pdf`,
|
name: `${name}_signed.pdf`,
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
arrayBuffer: async () => Promise.resolve(pdfBuffer),
|
||||||
|
|||||||
@ -12,7 +12,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 { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
import { extractDerivedDocumentEmailSettings } from '../../types/document-email';
|
||||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||||
import { env } from '../../utils/env';
|
import { env } from '../../utils/env';
|
||||||
import { renderCustomEmailTemplate } from '../../utils/render-custom-email-template';
|
import { renderCustomEmailTemplate } from '../../utils/render-custom-email-template';
|
||||||
@ -57,7 +57,7 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
|||||||
|
|
||||||
const { user: owner } = document;
|
const { user: owner } = document;
|
||||||
|
|
||||||
const completedDocument = await getFile(document.documentData);
|
const completedDocument = await getFileServerSide(document.documentData);
|
||||||
|
|
||||||
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import {
|
|||||||
|
|
||||||
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 { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
|
|
||||||
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
@ -19,7 +18,8 @@ import {
|
|||||||
ZWebhookDocumentSchema,
|
ZWebhookDocumentSchema,
|
||||||
mapDocumentToWebhookDocumentPayload,
|
mapDocumentToWebhookDocumentPayload,
|
||||||
} from '../../types/webhook-payload';
|
} from '../../types/webhook-payload';
|
||||||
import { getFile } from '../../universal/upload/get-file';
|
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||||
|
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||||
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
|
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
|
||||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ export const sendDocument = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (document.formValues) {
|
if (document.formValues) {
|
||||||
const file = await getFile(documentData);
|
const file = await getFileServerSide(documentData);
|
||||||
|
|
||||||
const prefilled = await insertFormValuesInPdf({
|
const prefilled = await insertFormValuesInPdf({
|
||||||
pdf: Buffer.from(file),
|
pdf: Buffer.from(file),
|
||||||
@ -114,7 +114,7 @@ export const sendDocument = async ({
|
|||||||
fileName = `${document.title}.pdf`;
|
fileName = `${document.title}.pdf`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDocumentData = await putPdfFile({
|
const newDocumentData = await putPdfFileServerSide({
|
||||||
name: fileName,
|
name: fileName,
|
||||||
type: 'application/pdf',
|
type: 'application/pdf',
|
||||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
|
||||||
export interface GetTemplateByDirectLinkTokenOptions {
|
export interface GetTemplateByDirectLinkTokenOptions {
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
@ -7,7 +9,7 @@ export interface GetTemplateByDirectLinkTokenOptions {
|
|||||||
export const getTemplateByDirectLinkToken = async ({
|
export const getTemplateByDirectLinkToken = async ({
|
||||||
token,
|
token,
|
||||||
}: GetTemplateByDirectLinkTokenOptions) => {
|
}: GetTemplateByDirectLinkTokenOptions) => {
|
||||||
const template = await prisma.template.findFirstOrThrow({
|
const template = await prisma.template.findFirst({
|
||||||
where: {
|
where: {
|
||||||
directLink: {
|
directLink: {
|
||||||
token,
|
token,
|
||||||
@ -26,8 +28,16 @@ export const getTemplateByDirectLinkToken = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const directLink = template?.directLink;
|
||||||
|
|
||||||
|
// Doing this to enforce type safety for directLink.
|
||||||
|
if (!directLink) {
|
||||||
|
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...template,
|
...template,
|
||||||
|
directLink,
|
||||||
fields: template.recipients.map((recipient) => recipient.fields).flat(),
|
fields: template.recipients.map((recipient) => recipient.fields).flat(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
50
packages/lib/universal/upload/get-file.server.ts
Normal file
50
packages/lib/universal/upload/get-file.server.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { DocumentDataType } from '@prisma/client';
|
||||||
|
import { base64 } from '@scure/base';
|
||||||
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
|
export type GetFileOptions = {
|
||||||
|
type: DocumentDataType;
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileServerSide = async ({ type, data }: GetFileOptions) => {
|
||||||
|
return await match(type)
|
||||||
|
.with(DocumentDataType.BYTES, () => getFileFromBytes(data))
|
||||||
|
.with(DocumentDataType.BYTES_64, () => getFileFromBytes64(data))
|
||||||
|
.with(DocumentDataType.S3_PATH, async () => getFileFromS3(data))
|
||||||
|
.exhaustive();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileFromBytes = (data: string) => {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
const binaryData = encoder.encode(data);
|
||||||
|
|
||||||
|
return binaryData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileFromBytes64 = (data: string) => {
|
||||||
|
const binaryData = base64.decode(data);
|
||||||
|
|
||||||
|
return binaryData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileFromS3 = async (key: string) => {
|
||||||
|
const { getPresignGetUrl } = await import('./server-actions');
|
||||||
|
|
||||||
|
const { url } = await getPresignGetUrl(key);
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to get file "${key}", failed with status code ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
|
||||||
|
const binaryData = new Uint8Array(buffer);
|
||||||
|
|
||||||
|
return binaryData;
|
||||||
|
};
|
||||||
@ -30,9 +30,23 @@ const getFileFromBytes64 = (data: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getFileFromS3 = async (key: string) => {
|
const getFileFromS3 = async (key: string) => {
|
||||||
const { getPresignGetUrl } = await import('./server-actions');
|
const getPresignedUrlResponse = await fetch(`/api/files/presigned-get-url`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
key,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
const { url } = await getPresignGetUrl(key);
|
if (!getPresignedUrlResponse.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get presigned url with key "${key}", failed with status code ${getPresignedUrlResponse.status}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url } = await getPresignedUrlResponse.json();
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
85
packages/lib/universal/upload/put-file.server.ts
Normal file
85
packages/lib/universal/upload/put-file.server.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { DocumentDataType } from '@prisma/client';
|
||||||
|
import { base64 } from '@scure/base';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
|
import { env } from '@documenso/lib/utils/env';
|
||||||
|
|
||||||
|
import { AppError } from '../../errors/app-error';
|
||||||
|
import { createDocumentData } from '../../server-only/document-data/create-document-data';
|
||||||
|
import { uploadS3File } from './server-actions';
|
||||||
|
|
||||||
|
type File = {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a document file to the appropriate storage location and creates
|
||||||
|
* a document data record.
|
||||||
|
*/
|
||||||
|
export const putPdfFileServerSide = async (file: File) => {
|
||||||
|
const isEncryptedDocumentsAllowed = false; // Was feature flag.
|
||||||
|
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
|
||||||
|
const pdf = await PDFDocument.load(arrayBuffer).catch((e) => {
|
||||||
|
console.error(`PDF upload parse error: ${e.message}`);
|
||||||
|
|
||||||
|
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
|
||||||
|
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.name.endsWith('.pdf')) {
|
||||||
|
file.name = `${file.name}.pdf`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, data } = await putFileServerSide(file);
|
||||||
|
|
||||||
|
return await createDocumentData({ type, data });
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a file to the appropriate storage location.
|
||||||
|
*/
|
||||||
|
export const putFileServerSide = async (file: File) => {
|
||||||
|
const NEXT_PUBLIC_UPLOAD_TRANSPORT = env('NEXT_PUBLIC_UPLOAD_TRANSPORT');
|
||||||
|
|
||||||
|
return await match(NEXT_PUBLIC_UPLOAD_TRANSPORT)
|
||||||
|
.with('s3', async () => putFileInS3(file))
|
||||||
|
.otherwise(async () => putFileInDatabase(file));
|
||||||
|
};
|
||||||
|
|
||||||
|
const putFileInDatabase = async (file: File) => {
|
||||||
|
const contents = await file.arrayBuffer();
|
||||||
|
|
||||||
|
const binaryData = new Uint8Array(contents);
|
||||||
|
|
||||||
|
const asciiData = base64.encode(binaryData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: DocumentDataType.BYTES_64,
|
||||||
|
data: asciiData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const putFileInS3 = async (file: File) => {
|
||||||
|
const buffer = await file.arrayBuffer();
|
||||||
|
|
||||||
|
const blob = new Blob([buffer], { type: file.type });
|
||||||
|
|
||||||
|
const newFile = new File([blob], file.name, {
|
||||||
|
type: file.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { key } = await uploadS3File(newFile);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: DocumentDataType.S3_PATH,
|
||||||
|
data: key,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,12 +1,15 @@
|
|||||||
import { DocumentDataType } from '@prisma/client';
|
import { DocumentDataType } from '@prisma/client';
|
||||||
import { base64 } from '@scure/base';
|
import { base64 } from '@scure/base';
|
||||||
import { PDFDocument } from 'pdf-lib';
|
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
import { env } from '@documenso/lib/utils/env';
|
import { env } from '@documenso/lib/utils/env';
|
||||||
|
import type {
|
||||||
|
TGetPresignedPostUrlResponse,
|
||||||
|
TUploadPdfResponse,
|
||||||
|
} from '@documenso/remix/server/api/files.types';
|
||||||
|
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||||
import { AppError } from '../../errors/app-error';
|
import { AppError } from '../../errors/app-error';
|
||||||
import { createDocumentData } from '../../server-only/document-data/create-document-data';
|
|
||||||
|
|
||||||
type File = {
|
type File = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -14,32 +17,29 @@ type File = {
|
|||||||
arrayBuffer: () => Promise<ArrayBuffer>;
|
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a document file to the appropriate storage location and creates
|
|
||||||
* a document data record.
|
|
||||||
*/
|
|
||||||
export const putPdfFile = async (file: File) => {
|
export const putPdfFile = async (file: File) => {
|
||||||
const isEncryptedDocumentsAllowed = false; // Was feature flag.
|
const formData = new FormData();
|
||||||
|
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
// Create a proper File object from the data
|
||||||
|
const buffer = await file.arrayBuffer();
|
||||||
|
const blob = new Blob([buffer], { type: file.type });
|
||||||
|
const properFile = new File([blob], file.name, { type: file.type });
|
||||||
|
|
||||||
const pdf = await PDFDocument.load(arrayBuffer).catch((e) => {
|
formData.append('file', properFile);
|
||||||
console.error(`PDF upload parse error: ${e.message}`);
|
|
||||||
|
|
||||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
const response = await fetch('/api/files/upload-pdf', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
|
if (!response.ok) {
|
||||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
console.error('Upload failed:', response.statusText);
|
||||||
|
throw new AppError('UPLOAD_FAILED');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.name.endsWith('.pdf')) {
|
const result: TUploadPdfResponse = await response.json();
|
||||||
file.name = `${file.name}.pdf`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { type, data } = await putFile(file);
|
return result;
|
||||||
|
|
||||||
return await createDocumentData({ type, data });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,9 +67,27 @@ const putFileInDatabase = async (file: File) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const putFileInS3 = async (file: File) => {
|
const putFileInS3 = async (file: File) => {
|
||||||
const { getPresignPostUrl } = await import('./server-actions');
|
const getPresignedUrlResponse = await fetch(
|
||||||
|
`${NEXT_PUBLIC_WEBAPP_URL()}/api/files/presigned-post-url`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
fileName: file.name,
|
||||||
|
contentType: file.type,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const { url, key } = await getPresignPostUrl(file.name, file.type);
|
if (!getPresignedUrlResponse.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get presigned post url, failed with status code ${getPresignedUrlResponse.status}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url, key }: TGetPresignedPostUrlResponse = await getPresignedUrlResponse.json();
|
||||||
|
|
||||||
const body = await file.arrayBuffer();
|
const body = await file.arrayBuffer();
|
||||||
|
|
||||||
|
|||||||
@ -9,37 +9,21 @@ import path from 'node:path';
|
|||||||
|
|
||||||
import { env } from '@documenso/lib/utils/env';
|
import { env } from '@documenso/lib/utils/env';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
|
||||||
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
|
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
|
||||||
import { alphaid } from '../id';
|
import { alphaid } from '../id';
|
||||||
|
|
||||||
export const getPresignPostUrl = async (fileName: string, contentType: string) => {
|
export const getPresignPostUrl = async (fileName: string, contentType: string, userId?: number) => {
|
||||||
const client = getS3Client();
|
const client = getS3Client();
|
||||||
|
|
||||||
const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
const { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
||||||
|
|
||||||
const token: { id: string } | null = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const baseUrl = NEXT_PUBLIC_WEBAPP_URL();
|
|
||||||
|
|
||||||
// Todo
|
|
||||||
// token = await getToken({
|
|
||||||
// req: new NextRequest(baseUrl, {
|
|
||||||
// headers: headers(),
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
} catch (err) {
|
|
||||||
// Non server-component environment
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the basename and extension for the file
|
// Get the basename and extension for the file
|
||||||
const { name, ext } = path.parse(fileName);
|
const { name, ext } = path.parse(fileName);
|
||||||
|
|
||||||
let key = `${alphaid(12)}/${slugify(name)}${ext}`;
|
let key = `${alphaid(12)}/${slugify(name)}${ext}`;
|
||||||
|
|
||||||
if (token) {
|
if (userId) {
|
||||||
key = `${token.id}/${key}`;
|
key = `${userId}/${key}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const putObjectCommand = new PutObjectCommand({
|
const putObjectCommand = new PutObjectCommand({
|
||||||
@ -104,6 +88,31 @@ export const getPresignGetUrl = async (key: string) => {
|
|||||||
return { key, url };
|
return { key, url };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a file to S3.
|
||||||
|
*/
|
||||||
|
export const uploadS3File = async (file: File) => {
|
||||||
|
const client = getS3Client();
|
||||||
|
|
||||||
|
// Get the basename and extension for the file
|
||||||
|
const { name, ext } = path.parse(file.name);
|
||||||
|
|
||||||
|
const key = `${alphaid(12)}/${slugify(name)}${ext}`;
|
||||||
|
|
||||||
|
const fileBuffer = await file.arrayBuffer();
|
||||||
|
|
||||||
|
const response = await client.send(
|
||||||
|
new PutObjectCommand({
|
||||||
|
Bucket: env('NEXT_PRIVATE_UPLOAD_BUCKET'),
|
||||||
|
Key: key,
|
||||||
|
Body: Buffer.from(fileBuffer),
|
||||||
|
ContentType: file.type,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return { key, response };
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteS3File = async (key: string) => {
|
export const deleteS3File = async (key: string) => {
|
||||||
const client = getS3Client();
|
const client = getS3Client();
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^5.4.2",
|
"@prisma/client": "^5.4.2",
|
||||||
"kysely": "^0.27.3",
|
"kysely": "0.26.3",
|
||||||
"prisma": "^5.4.2",
|
"prisma": "^5.4.2",
|
||||||
"prisma-extension-kysely": "^2.1.0",
|
"prisma-extension-kysely": "^2.1.0",
|
||||||
"ts-pattern": "^5.0.6"
|
"ts-pattern": "^5.0.6"
|
||||||
|
|||||||
4
packages/tailwind-config/index.d.ts
vendored
Normal file
4
packages/tailwind-config/index.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import type { Config } from 'tailwindcss';
|
||||||
|
|
||||||
|
declare const config: Config;
|
||||||
|
export default config;
|
||||||
@ -2,6 +2,7 @@
|
|||||||
"name": "@documenso/tailwind-config",
|
"name": "@documenso/tailwind-config",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"main": "index.cjs",
|
"main": "index.cjs",
|
||||||
|
"types": "index.d.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf node_modules"
|
"clean": "rimraf node_modules"
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { SessionUser } from '@documenso/auth/server/lib/session/session';
|
||||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import type { Session, User } from '@documenso/prisma/client';
|
import type { Session } from '@documenso/prisma/client';
|
||||||
|
|
||||||
type CreateTrpcContextOptions = {
|
type CreateTrpcContextOptions = {
|
||||||
c: Context;
|
c: Context;
|
||||||
@ -59,7 +60,7 @@ export type TrpcContext = (
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
session: Session;
|
session: Session;
|
||||||
user: User;
|
user: SessionUser;
|
||||||
}
|
}
|
||||||
) & {
|
) & {
|
||||||
teamId: number | undefined;
|
teamId: number | undefined;
|
||||||
|
|||||||
@ -67,13 +67,6 @@ import { FieldAdvancedSettings } from './field-item-advanced-settings';
|
|||||||
import { MissingSignatureFieldDialog } from './missing-signature-field-dialog';
|
import { MissingSignatureFieldDialog } from './missing-signature-field-dialog';
|
||||||
import { type DocumentFlowStep, FRIENDLY_FIELD_TYPE } from './types';
|
import { type DocumentFlowStep, FRIENDLY_FIELD_TYPE } from './types';
|
||||||
|
|
||||||
// const fontCaveat = Caveat({
|
|
||||||
// weight: ['500'],
|
|
||||||
// subsets: ['latin'],
|
|
||||||
// display: 'swap',
|
|
||||||
// variable: '--font-caveat',
|
|
||||||
// });
|
|
||||||
|
|
||||||
const MIN_HEIGHT_PX = 12;
|
const MIN_HEIGHT_PX = 12;
|
||||||
const MIN_WIDTH_PX = 36;
|
const MIN_WIDTH_PX = 36;
|
||||||
|
|
||||||
@ -541,12 +534,6 @@ export const AddFieldsFormPartial = ({
|
|||||||
);
|
);
|
||||||
}, [recipientsByRole]);
|
}, [recipientsByRole]);
|
||||||
|
|
||||||
const isTypedSignatureEnabled = form.watch('typedSignatureEnabled');
|
|
||||||
|
|
||||||
const handleTypedSignatureChange = (value: boolean) => {
|
|
||||||
form.setValue('typedSignatureEnabled', value, { shouldDirty: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAdvancedSettings = () => {
|
const handleAdvancedSettings = () => {
|
||||||
setShowAdvancedSettings((prev) => !prev);
|
setShowAdvancedSettings((prev) => !prev);
|
||||||
};
|
};
|
||||||
@ -831,8 +818,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
'text-muted-foreground group-data-[selected]:text-foreground font-signature flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||||
// fontCaveat.className,
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Trans>Signature</Trans>
|
<Trans>Signature</Trans>
|
||||||
|
|||||||
@ -20,15 +20,13 @@ export const CheckboxField = ({ field }: CheckboxFieldProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsedFieldMeta && (!parsedFieldMeta.values || parsedFieldMeta.values.length === 0)) {
|
if (parsedFieldMeta && (!parsedFieldMeta.values || parsedFieldMeta.values.length === 0)) {
|
||||||
return (
|
return <FieldIcon fieldMeta={field.fieldMeta} type={field.type} />;
|
||||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="flex flex-col gap-y-1">
|
||||||
{!parsedFieldMeta?.values ? (
|
{!parsedFieldMeta?.values ? (
|
||||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} />
|
||||||
) : (
|
) : (
|
||||||
parsedFieldMeta.values.map((item: { value: string; checked: boolean }, index: number) => (
|
parsedFieldMeta.values.map((item: { value: string; checked: boolean }, index: number) => (
|
||||||
<div key={index} className="flex items-center gap-x-1.5">
|
<div key={index} className="flex items-center gap-x-1.5">
|
||||||
|
|||||||
@ -20,15 +20,13 @@ export const RadioField = ({ field }: RadioFieldProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parsedFieldMeta && (!parsedFieldMeta.values || parsedFieldMeta.values.length === 0)) {
|
if (parsedFieldMeta && (!parsedFieldMeta.values || parsedFieldMeta.values.length === 0)) {
|
||||||
return (
|
return <FieldIcon fieldMeta={field.fieldMeta} type={field.type} />;
|
||||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-2">
|
<div className="flex flex-col gap-y-2">
|
||||||
{!parsedFieldMeta?.values ? (
|
{!parsedFieldMeta?.values ? (
|
||||||
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} signerEmail={field.signerEmail} />
|
<FieldIcon fieldMeta={field.fieldMeta} type={field.type} />
|
||||||
) : (
|
) : (
|
||||||
<RadioGroup className="gap-y-1">
|
<RadioGroup className="gap-y-1">
|
||||||
{parsedFieldMeta.values?.map((item, index) => (
|
{parsedFieldMeta.values?.map((item, index) => (
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import { Eye, EyeOff } from 'lucide-react';
|
|||||||
|
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { Button } from './button';
|
import { Button } from './button';
|
||||||
import { Input, InputProps } from './input';
|
import type { InputProps } from './input';
|
||||||
|
import { Input } from './input';
|
||||||
|
|
||||||
const PasswordInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'type'>>(
|
const PasswordInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'type'>>(
|
||||||
({ className, ...props }, ref) => {
|
({ className, ...props }, ref) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {
|
import type {
|
||||||
MouseEvent as ReactMouseEvent,
|
MouseEvent as ReactMouseEvent,
|
||||||
PointerEvent as ReactPointerEvent,
|
PointerEvent as ReactPointerEvent,
|
||||||
TouchEvent as ReactTouchEvent,
|
TouchEvent as ReactTouchEvent,
|
||||||
|
|||||||
@ -20,14 +20,6 @@ import { cn } from '../../lib/utils';
|
|||||||
import { getSvgPathFromStroke } from './helper';
|
import { getSvgPathFromStroke } from './helper';
|
||||||
import { Point } from './point';
|
import { Point } from './point';
|
||||||
|
|
||||||
// Todo
|
|
||||||
// const fontCaveat = Caveat({
|
|
||||||
// weight: ['500'],
|
|
||||||
// subsets: ['latin'],
|
|
||||||
// display: 'swap',
|
|
||||||
// variable: '--font-caveat',
|
|
||||||
// });
|
|
||||||
|
|
||||||
const DPI = 2;
|
const DPI = 2;
|
||||||
|
|
||||||
const isBase64Image = (value: string) => value.startsWith('data:image/png;base64,');
|
const isBase64Image = (value: string) => value.startsWith('data:image/png;base64,');
|
||||||
@ -309,8 +301,7 @@ export const SignaturePad = ({
|
|||||||
if (ctx) {
|
if (ctx) {
|
||||||
const canvasWidth = $el.current.width;
|
const canvasWidth = $el.current.width;
|
||||||
const canvasHeight = $el.current.height;
|
const canvasHeight = $el.current.height;
|
||||||
// const fontFamily = String(fontCaveat.style.fontFamily);
|
const fontFamily = 'Caveat';
|
||||||
const fontFamily = 'sans-serif';
|
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
|
|||||||
@ -61,13 +61,6 @@ import { Form, FormControl, FormField, FormItem, FormLabel } from '../form/form'
|
|||||||
import { useStep } from '../stepper';
|
import { useStep } from '../stepper';
|
||||||
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
import type { TAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
||||||
|
|
||||||
// const fontCaveat = Caveat({
|
|
||||||
// weight: ['500'],
|
|
||||||
// subsets: ['latin'],
|
|
||||||
// display: 'swap',
|
|
||||||
// variable: '--font-caveat',
|
|
||||||
// });
|
|
||||||
|
|
||||||
const MIN_HEIGHT_PX = 12;
|
const MIN_HEIGHT_PX = 12;
|
||||||
const MIN_WIDTH_PX = 36;
|
const MIN_WIDTH_PX = 36;
|
||||||
|
|
||||||
@ -696,8 +689,7 @@ export const AddTemplateFieldsFormPartial = ({
|
|||||||
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-muted-foreground group-data-[selected]:text-foreground flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
'text-muted-foreground group-data-[selected]:text-foreground font-signature flex items-center justify-center gap-x-1.5 text-lg font-normal',
|
||||||
// fontCaveat.className,
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Trans>Signature</Trans>
|
<Trans>Signature</Trans>
|
||||||
|
|||||||
Reference in New Issue
Block a user