mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 19:21:39 +10:00
chore: remove duplicates
This commit is contained in:
@ -12,7 +12,7 @@ import { match } from 'ts-pattern';
|
|||||||
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||||
import { useCurrentEnvelopeRender } from '@documenso/lib/client-only/providers/envelope-render-provider';
|
import { useCurrentEnvelopeRender } from '@documenso/lib/client-only/providers/envelope-render-provider';
|
||||||
import { getPageCanvasRefs } from '@documenso/lib/client-only/utils/page-canvas-registry';
|
import { getPageCanvasRefs } from '@documenso/lib/client-only/utils/page-canvas-registry';
|
||||||
import type { TDetectedFormField } from '@documenso/lib/types/ai';
|
import type { TDetectedFormField } from '@documenso/lib/types/document-analysis';
|
||||||
import type {
|
import type {
|
||||||
TCheckboxFieldMeta,
|
TCheckboxFieldMeta,
|
||||||
TDateFieldMeta,
|
TDateFieldMeta,
|
||||||
|
|||||||
@ -1,108 +1,30 @@
|
|||||||
// sort-imports-ignore
|
|
||||||
|
|
||||||
// ---- PATCH pdfjs-dist's canvas require BEFORE importing it ----
|
|
||||||
import { createRequire } from 'node:module';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import { Canvas, Image } from 'skia-canvas';
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url || fileURLToPath(new URL('.', import.meta.url)));
|
|
||||||
const Module = require('node:module');
|
|
||||||
|
|
||||||
const originalRequire = Module.prototype.require;
|
|
||||||
Module.prototype.require = function (path: string) {
|
|
||||||
if (path === 'canvas') {
|
|
||||||
return {
|
|
||||||
createCanvas: (width: number, height: number) => new Canvas(width, height),
|
|
||||||
Image, // needed by pdfjs-dist
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line prefer-rest-params, @typescript-eslint/consistent-type-assertions
|
|
||||||
return originalRequire.apply(this, arguments as unknown as [string]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use dynamic require to bypass Vite SSR transformation
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const pdfjsLib = require('pdfjs-dist/legacy/build/pdf.js');
|
|
||||||
|
|
||||||
import { mkdir, writeFile } from 'node:fs/promises';
|
|
||||||
import { join } from 'node:path';
|
|
||||||
|
|
||||||
import { generateObject } from 'ai';
|
import { generateObject } from 'ai';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import sharp from 'sharp';
|
import { mkdir, writeFile } from 'node:fs/promises';
|
||||||
import { z } from 'zod';
|
import { join } from 'node:path';
|
||||||
|
import { Canvas, Image } from 'skia-canvas';
|
||||||
|
|
||||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||||
|
import { resizeAndCompressImage } from '@documenso/lib/server-only/image/resize-and-compress-image';
|
||||||
|
import { renderPdfToImage } from '@documenso/lib/server-only/pdf/render-pdf-to-image';
|
||||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||||
import { env } from '@documenso/lib/utils/env';
|
import { env } from '@documenso/lib/utils/env';
|
||||||
|
import { resolveRecipientEmail } from '@documenso/lib/utils/recipients';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
|
|
||||||
import { ANALYZE_RECIPIENTS_PROMPT, DETECT_OBJECTS_PROMPT } from './ai.prompts';
|
import type { HonoEnv } from '../../router';
|
||||||
import type { HonoEnv } from '../router';
|
import { ANALYZE_RECIPIENTS_PROMPT, DETECT_OBJECTS_PROMPT } from './prompts';
|
||||||
import {
|
import {
|
||||||
type TAnalyzeRecipientsResponse,
|
type TAnalyzeRecipientsResponse,
|
||||||
type TDetectedRecipient,
|
|
||||||
type TDetectFormFieldsResponse,
|
type TDetectFormFieldsResponse,
|
||||||
|
type TDetectedRecipient,
|
||||||
ZAnalyzeRecipientsRequestSchema,
|
ZAnalyzeRecipientsRequestSchema,
|
||||||
ZDetectedRecipientLLMSchema,
|
|
||||||
ZDetectedFormFieldSchema,
|
|
||||||
ZDetectFormFieldsRequestSchema,
|
ZDetectFormFieldsRequestSchema,
|
||||||
} from './ai.types';
|
ZDetectedFormFieldSchema,
|
||||||
|
ZDetectedRecipientLLMSchema,
|
||||||
const renderPdfToImage = async (pdfBytes: Uint8Array) => {
|
} from './types';
|
||||||
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
|
|
||||||
const pdf = await loadingTask.promise;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const scale = 4;
|
|
||||||
|
|
||||||
const pages = await Promise.all(
|
|
||||||
Array.from({ length: pdf.numPages }, async (_, index) => {
|
|
||||||
const pageNumber = index + 1;
|
|
||||||
const page = await pdf.getPage(pageNumber);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const viewport = page.getViewport({ scale });
|
|
||||||
|
|
||||||
const virtualCanvas = new Canvas(viewport.width, viewport.height);
|
|
||||||
const context = virtualCanvas.getContext('2d');
|
|
||||||
context.imageSmoothingEnabled = false;
|
|
||||||
|
|
||||||
await page.render({ canvasContext: context, viewport }).promise;
|
|
||||||
|
|
||||||
return {
|
|
||||||
image: await virtualCanvas.toBuffer('png'),
|
|
||||||
pageNumber,
|
|
||||||
width: Math.floor(viewport.width),
|
|
||||||
height: Math.floor(viewport.height),
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
page.cleanup();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return pages;
|
|
||||||
} finally {
|
|
||||||
await pdf.destroy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const resizeAndCompressImage = async (imageBuffer: Buffer): Promise<Buffer> => {
|
|
||||||
const metadata = await sharp(imageBuffer).metadata();
|
|
||||||
const originalWidth = metadata.width || 0;
|
|
||||||
|
|
||||||
if (originalWidth > 1000) {
|
|
||||||
return await sharp(imageBuffer)
|
|
||||||
.resize({ width: 1000, withoutEnlargement: true })
|
|
||||||
.jpeg({ quality: 70 })
|
|
||||||
.toBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
return await sharp(imageBuffer).jpeg({ quality: 70 }).toBuffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
type FieldDetectionRecipient = {
|
type FieldDetectionRecipient = {
|
||||||
id: number;
|
id: number;
|
||||||
@ -203,20 +125,6 @@ const runFormFieldDetection = async (
|
|||||||
// Limit recipient detection to first 3 pages for performance and cost efficiency
|
// Limit recipient detection to first 3 pages for performance and cost efficiency
|
||||||
const MAX_PAGES_FOR_RECIPIENT_ANALYSIS = 3;
|
const MAX_PAGES_FOR_RECIPIENT_ANALYSIS = 3;
|
||||||
|
|
||||||
const recipientEmailSchema = z.string().email();
|
|
||||||
|
|
||||||
const resolveRecipientEmail = (candidateEmail: string | undefined) => {
|
|
||||||
if (candidateEmail) {
|
|
||||||
const trimmedEmail = candidateEmail.trim();
|
|
||||||
|
|
||||||
if (recipientEmailSchema.safeParse(trimmedEmail).success) {
|
|
||||||
return trimmedEmail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const authorizeDocumentAccess = async (envelopeId: string, userId: number) => {
|
const authorizeDocumentAccess = async (envelopeId: string, userId: number) => {
|
||||||
const envelope = await prisma.envelope.findUnique({
|
const envelope = await prisma.envelope.findUnique({
|
||||||
where: { id: envelopeId },
|
where: { id: envelopeId },
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import type { TDetectedFormField } from '@documenso/lib/types/ai';
|
import type { TDetectedFormField } from '@documenso/lib/types/document-analysis';
|
||||||
|
|
||||||
export const ZGenerateTextRequestSchema = z.object({
|
export const ZGenerateTextRequestSchema = z.object({
|
||||||
prompt: z.string().min(1, 'Prompt is required').max(5000, 'Prompt is too long'),
|
prompt: z.string().min(1, 'Prompt is required').max(5000, 'Prompt is too long'),
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { getIpAddress } from '@documenso/lib/universal/get-ip-address';
|
|||||||
import { logger } from '@documenso/lib/utils/logger';
|
import { logger } from '@documenso/lib/utils/logger';
|
||||||
import { openApiDocument } from '@documenso/trpc/server/open-api';
|
import { openApiDocument } from '@documenso/trpc/server/open-api';
|
||||||
|
|
||||||
import { aiRoute } from './api/ai';
|
import { aiRoute } from './api/document-analysis/index';
|
||||||
import { downloadRoute } from './api/download/download';
|
import { downloadRoute } from './api/download/download';
|
||||||
import { filesRoute } from './api/files/files';
|
import { filesRoute } from './api/files/files';
|
||||||
import { type AppContext, appContext } from './context';
|
import { type AppContext, appContext } from './context';
|
||||||
|
|||||||
15
packages/lib/server-only/image/resize-and-compress-image.ts
Normal file
15
packages/lib/server-only/image/resize-and-compress-image.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import sharp from 'sharp';
|
||||||
|
|
||||||
|
export const resizeAndCompressImage = async (imageBuffer: Buffer): Promise<Buffer> => {
|
||||||
|
const metadata = await sharp(imageBuffer).metadata();
|
||||||
|
const originalWidth = metadata.width || 0;
|
||||||
|
|
||||||
|
if (originalWidth > 1000) {
|
||||||
|
return await sharp(imageBuffer)
|
||||||
|
.resize({ width: 1000, withoutEnlargement: true })
|
||||||
|
.jpeg({ quality: 70 })
|
||||||
|
.toBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await sharp(imageBuffer).jpeg({ quality: 70 }).toBuffer();
|
||||||
|
};
|
||||||
61
packages/lib/server-only/pdf/render-pdf-to-image.ts
Normal file
61
packages/lib/server-only/pdf/render-pdf-to-image.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { createRequire } from 'node:module';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { Canvas, Image } from 'skia-canvas';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url || fileURLToPath(new URL('.', import.meta.url)));
|
||||||
|
const Module = require('node:module');
|
||||||
|
|
||||||
|
const originalRequire = Module.prototype.require;
|
||||||
|
Module.prototype.require = function (path: string) {
|
||||||
|
if (path === 'canvas') {
|
||||||
|
return {
|
||||||
|
createCanvas: (width: number, height: number) => new Canvas(width, height),
|
||||||
|
Image, // needed by pdfjs-dist
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line prefer-rest-params, @typescript-eslint/consistent-type-assertions
|
||||||
|
return originalRequire.apply(this, arguments as unknown as [string]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use dynamic require to bypass Vite SSR transformation
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const pdfjsLib = require('pdfjs-dist/legacy/build/pdf.js');
|
||||||
|
|
||||||
|
export const renderPdfToImage = async (pdfBytes: Uint8Array) => {
|
||||||
|
const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
|
||||||
|
const pdf = await loadingTask.promise;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const scale = 4;
|
||||||
|
|
||||||
|
const pages = await Promise.all(
|
||||||
|
Array.from({ length: pdf.numPages }, async (_, index) => {
|
||||||
|
const pageNumber = index + 1;
|
||||||
|
const page = await pdf.getPage(pageNumber);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const viewport = page.getViewport({ scale });
|
||||||
|
|
||||||
|
const virtualCanvas = new Canvas(viewport.width, viewport.height);
|
||||||
|
const context = virtualCanvas.getContext('2d');
|
||||||
|
context.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
|
await page.render({ canvasContext: context, viewport }).promise;
|
||||||
|
|
||||||
|
return {
|
||||||
|
image: await virtualCanvas.toBuffer('png'),
|
||||||
|
pageNumber,
|
||||||
|
width: Math.floor(viewport.width),
|
||||||
|
height: Math.floor(viewport.height),
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
page.cleanup();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
} finally {
|
||||||
|
await pdf.destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import type { Envelope } from '@prisma/client';
|
import type { Envelope } from '@prisma/client';
|
||||||
import { type Field, type Recipient, RecipientRole, SigningStatus } from '@prisma/client';
|
import { type Field, type Recipient, RecipientRole, SigningStatus } from '@prisma/client';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
|
||||||
import { extractLegacyIds } from '../universal/id';
|
import { extractLegacyIds } from '../universal/id';
|
||||||
@ -8,6 +9,18 @@ const UNKNOWN_RECIPIENT_NAME_PLACEHOLDER = '<UNKNOWN>';
|
|||||||
|
|
||||||
export const formatSigningLink = (token: string) => `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${token}`;
|
export const formatSigningLink = (token: string) => `${NEXT_PUBLIC_WEBAPP_URL()}/sign/${token}`;
|
||||||
|
|
||||||
|
export const resolveRecipientEmail = (candidateEmail: string | undefined | null) => {
|
||||||
|
if (candidateEmail) {
|
||||||
|
const trimmedEmail = candidateEmail.trim();
|
||||||
|
|
||||||
|
if (z.string().email().safeParse(trimmedEmail).success) {
|
||||||
|
return trimmedEmail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a recipient can be modified by the document owner.
|
* Whether a recipient can be modified by the document owner.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user