mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
feat: migrate nextjs to rr7
This commit is contained in:
@ -1,6 +1,3 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
|
||||
import type { RequestInternal } from 'next-auth';
|
||||
import { z } from 'zod';
|
||||
|
||||
const ZIpSchema = z.string().ip();
|
||||
@ -42,34 +39,14 @@ export type ApiRequestMetadata = {
|
||||
};
|
||||
};
|
||||
|
||||
export const extractNextApiRequestMetadata = (req: NextApiRequest): RequestMetadata => {
|
||||
const parsedIp = ZIpSchema.safeParse(req.headers['x-forwarded-for'] || req.socket.remoteAddress);
|
||||
export const extractRequestMetadata = (req: Request): RequestMetadata => {
|
||||
const parsedIp = ZIpSchema.safeParse(req.headers.get('x-forwarded-for'));
|
||||
|
||||
const ipAddress = parsedIp.success ? parsedIp.data : undefined;
|
||||
const userAgent = req.headers['user-agent'];
|
||||
const userAgent = req.headers.get('user-agent');
|
||||
|
||||
return {
|
||||
ipAddress,
|
||||
userAgent,
|
||||
};
|
||||
};
|
||||
|
||||
export const extractNextAuthRequestMetadata = (
|
||||
req: Pick<RequestInternal, 'body' | 'query' | 'headers' | 'method'>,
|
||||
): RequestMetadata => {
|
||||
return extractNextHeaderRequestMetadata(req.headers ?? {});
|
||||
};
|
||||
|
||||
export const extractNextHeaderRequestMetadata = (
|
||||
headers: Record<string, string>,
|
||||
): RequestMetadata => {
|
||||
const parsedIp = ZIpSchema.safeParse(headers?.['x-forwarded-for']);
|
||||
|
||||
const ipAddress = parsedIp.success ? parsedIp.data : undefined;
|
||||
const userAgent = headers?.['user-agent'];
|
||||
|
||||
return {
|
||||
ipAddress,
|
||||
userAgent,
|
||||
userAgent: userAgent ?? undefined,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../constants/app';
|
||||
import { env } from '../utils/env';
|
||||
|
||||
export const getBaseUrl = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (process.env.VERCEL_URL) {
|
||||
return `https://${process.env.VERCEL_URL}`;
|
||||
if (env('VERCEL_URL')) {
|
||||
return `https://${env('VERCEL_URL')}`;
|
||||
}
|
||||
|
||||
const webAppUrl = NEXT_PUBLIC_WEBAPP_URL();
|
||||
@ -16,5 +17,5 @@ export const getBaseUrl = () => {
|
||||
return webAppUrl;
|
||||
}
|
||||
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
return `http://localhost:${env('PORT') ?? 3000}`;
|
||||
};
|
||||
|
||||
@ -1,111 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { TFeatureFlagValue } from '@documenso/lib/client-only/providers/feature-flag.types';
|
||||
import { ZFeatureFlagValueSchema } from '@documenso/lib/client-only/providers/feature-flag.types';
|
||||
import { APP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { LOCAL_FEATURE_FLAGS, isFeatureFlagEnabled } from '@documenso/lib/constants/feature-flags';
|
||||
|
||||
/**
|
||||
* Evaluate whether a flag is enabled for the current user.
|
||||
*
|
||||
* @param flag The flag to evaluate.
|
||||
* @param options See `GetFlagOptions`.
|
||||
* @returns Whether the flag is enabled, or the variant value of the flag.
|
||||
*/
|
||||
export const getFlag = async (
|
||||
flag: string,
|
||||
options?: GetFlagOptions,
|
||||
): Promise<TFeatureFlagValue> => {
|
||||
const requestHeaders = options?.requestHeaders ?? {};
|
||||
delete requestHeaders['content-length'];
|
||||
|
||||
if (!isFeatureFlagEnabled()) {
|
||||
return LOCAL_FEATURE_FLAGS[flag] ?? true;
|
||||
}
|
||||
|
||||
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/get`);
|
||||
url.searchParams.set('flag', flag);
|
||||
|
||||
return await fetch(url, {
|
||||
headers: {
|
||||
...requestHeaders,
|
||||
},
|
||||
next: {
|
||||
revalidate: 60,
|
||||
},
|
||||
})
|
||||
.then(async (res) => res.json())
|
||||
.then((res) => ZFeatureFlagValueSchema.parse(res))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return LOCAL_FEATURE_FLAGS[flag] ?? false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all feature flags for the current user if possible.
|
||||
*
|
||||
* @param options See `GetFlagOptions`.
|
||||
* @returns A record of flags and their values for the user derived from the headers.
|
||||
*/
|
||||
export const getAllFlags = async (
|
||||
options?: GetFlagOptions,
|
||||
): Promise<Record<string, TFeatureFlagValue>> => {
|
||||
const requestHeaders = options?.requestHeaders ?? {};
|
||||
delete requestHeaders['content-length'];
|
||||
|
||||
if (!isFeatureFlagEnabled()) {
|
||||
return LOCAL_FEATURE_FLAGS;
|
||||
}
|
||||
|
||||
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/all`);
|
||||
|
||||
return fetch(url, {
|
||||
headers: {
|
||||
...requestHeaders,
|
||||
},
|
||||
next: {
|
||||
revalidate: 60,
|
||||
},
|
||||
})
|
||||
.then(async (res) => res.json())
|
||||
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return LOCAL_FEATURE_FLAGS;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all feature flags for anonymous users.
|
||||
*
|
||||
* @returns A record of flags and their values.
|
||||
*/
|
||||
export const getAllAnonymousFlags = async (): Promise<Record<string, TFeatureFlagValue>> => {
|
||||
if (!isFeatureFlagEnabled()) {
|
||||
return LOCAL_FEATURE_FLAGS;
|
||||
}
|
||||
|
||||
const url = new URL(`${APP_BASE_URL()}/api/feature-flag/all`);
|
||||
|
||||
return fetch(url, {
|
||||
next: {
|
||||
revalidate: 60,
|
||||
},
|
||||
})
|
||||
.then(async (res) => res.json())
|
||||
.then((res) => z.record(z.string(), ZFeatureFlagValueSchema).parse(res))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return LOCAL_FEATURE_FLAGS;
|
||||
});
|
||||
};
|
||||
|
||||
interface GetFlagOptions {
|
||||
/**
|
||||
* The headers to attach to the request to evaluate flags.
|
||||
*
|
||||
* The authenticated user will be derived from the headers if possible.
|
||||
*/
|
||||
requestHeaders: Record<string, string>;
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DocumentDataType } from '@documenso/prisma/client';
|
||||
|
||||
import { deleteS3File } from './server-actions';
|
||||
|
||||
export type DeleteFileOptions = {
|
||||
|
||||
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;
|
||||
};
|
||||
@ -1,8 +1,7 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { base64 } from '@scure/base';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DocumentDataType } from '@documenso/prisma/client';
|
||||
|
||||
export type GetFileOptions = {
|
||||
type: DocumentDataType;
|
||||
data: string;
|
||||
@ -31,9 +30,23 @@ const getFileFromBytes64 = (data: 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, {
|
||||
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,13 +1,15 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { base64 } from '@scure/base';
|
||||
import { env } from 'next-runtime-env';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { getFlag } from '@documenso/lib/universal/get-feature-flag';
|
||||
import { DocumentDataType } from '@documenso/prisma/client';
|
||||
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 { createDocumentData } from '../../server-only/document-data/create-document-data';
|
||||
|
||||
type File = {
|
||||
name: string;
|
||||
@ -15,34 +17,29 @@ type File = {
|
||||
arrayBuffer: () => Promise<ArrayBuffer>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads a document file to the appropriate storage location and creates
|
||||
* a document data record.
|
||||
*/
|
||||
export const putPdfFile = async (file: File) => {
|
||||
const isEncryptedDocumentsAllowed = await getFlag('app_allow_encrypted_documents').catch(
|
||||
() => false,
|
||||
);
|
||||
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) => {
|
||||
console.error(`PDF upload parse error: ${e.message}`);
|
||||
formData.append('file', properFile);
|
||||
|
||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||
const response = await fetch('/api/files/upload-pdf', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!isEncryptedDocumentsAllowed && pdf.isEncrypted) {
|
||||
throw new AppError('INVALID_DOCUMENT_FILE');
|
||||
if (!response.ok) {
|
||||
console.error('Upload failed:', response.statusText);
|
||||
throw new AppError('UPLOAD_FAILED');
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.pdf')) {
|
||||
file.name = `${file.name}.pdf`;
|
||||
}
|
||||
const result: TUploadPdfResponse = await response.json();
|
||||
|
||||
const { type, data } = await putFile(file);
|
||||
|
||||
return await createDocumentData({ type, data });
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -70,9 +67,27 @@ const putFileInDatabase = 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();
|
||||
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
'use server';
|
||||
|
||||
import { headers } from 'next/headers';
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
import {
|
||||
DeleteObjectCommand,
|
||||
GetObjectCommand,
|
||||
@ -10,44 +5,29 @@ import {
|
||||
S3Client,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import slugify from '@sindresorhus/slugify';
|
||||
import { type JWT, getToken } from 'next-auth/jwt';
|
||||
import { env } from 'next-runtime-env';
|
||||
import path from 'node:path';
|
||||
|
||||
import { APP_BASE_URL } from '../../constants/app';
|
||||
import { env } from '@documenso/lib/utils/env';
|
||||
|
||||
import { ONE_HOUR, ONE_SECOND } from '../../constants/time';
|
||||
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 { getSignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
||||
|
||||
let token: JWT | null = null;
|
||||
|
||||
try {
|
||||
const baseUrl = APP_BASE_URL() ?? 'http://localhost:3000';
|
||||
|
||||
token = await getToken({
|
||||
req: new NextRequest(baseUrl, {
|
||||
headers: headers(),
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
// Non server-component environment
|
||||
}
|
||||
|
||||
// Get the basename and extension for the file
|
||||
const { name, ext } = path.parse(fileName);
|
||||
|
||||
let key = `${alphaid(12)}/${slugify(name)}${ext}`;
|
||||
|
||||
if (token) {
|
||||
key = `${token.id}/${key}`;
|
||||
if (userId) {
|
||||
key = `${userId}/${key}`;
|
||||
}
|
||||
|
||||
const putObjectCommand = new PutObjectCommand({
|
||||
Bucket: process.env.NEXT_PRIVATE_UPLOAD_BUCKET,
|
||||
Bucket: env('NEXT_PRIVATE_UPLOAD_BUCKET'),
|
||||
Key: key,
|
||||
ContentType: contentType,
|
||||
});
|
||||
@ -65,7 +45,7 @@ export const getAbsolutePresignPostUrl = async (key: string) => {
|
||||
const { getSignedUrl: getS3SignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
||||
|
||||
const putObjectCommand = new PutObjectCommand({
|
||||
Bucket: process.env.NEXT_PRIVATE_UPLOAD_BUCKET,
|
||||
Bucket: env('NEXT_PRIVATE_UPLOAD_BUCKET'),
|
||||
Key: key,
|
||||
});
|
||||
|
||||
@ -77,15 +57,15 @@ export const getAbsolutePresignPostUrl = async (key: string) => {
|
||||
};
|
||||
|
||||
export const getPresignGetUrl = async (key: string) => {
|
||||
if (process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_DOMAIN) {
|
||||
const distributionUrl = new URL(key, `${process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_DOMAIN}`);
|
||||
if (env('NEXT_PRIVATE_UPLOAD_DISTRIBUTION_DOMAIN')) {
|
||||
const distributionUrl = new URL(key, `${env('NEXT_PRIVATE_UPLOAD_DISTRIBUTION_DOMAIN')}`);
|
||||
|
||||
const { getSignedUrl: getCloudfrontSignedUrl } = await import('@aws-sdk/cloudfront-signer');
|
||||
|
||||
const url = getCloudfrontSignedUrl({
|
||||
url: distributionUrl.toString(),
|
||||
keyPairId: `${process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_ID}`,
|
||||
privateKey: `${process.env.NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_CONTENTS}`,
|
||||
keyPairId: `${env('NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_ID')}`,
|
||||
privateKey: `${env('NEXT_PRIVATE_UPLOAD_DISTRIBUTION_KEY_CONTENTS')}`,
|
||||
dateLessThan: new Date(Date.now() + ONE_HOUR).toISOString(),
|
||||
});
|
||||
|
||||
@ -97,7 +77,7 @@ export const getPresignGetUrl = async (key: string) => {
|
||||
const { getSignedUrl: getS3SignedUrl } = await import('@aws-sdk/s3-request-presigner');
|
||||
|
||||
const getObjectCommand = new GetObjectCommand({
|
||||
Bucket: process.env.NEXT_PRIVATE_UPLOAD_BUCKET,
|
||||
Bucket: env('NEXT_PRIVATE_UPLOAD_BUCKET'),
|
||||
Key: key,
|
||||
});
|
||||
|
||||
@ -108,12 +88,37 @@ export const getPresignGetUrl = async (key: string) => {
|
||||
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) => {
|
||||
const client = getS3Client();
|
||||
|
||||
await client.send(
|
||||
new DeleteObjectCommand({
|
||||
Bucket: process.env.NEXT_PRIVATE_UPLOAD_BUCKET,
|
||||
Bucket: env('NEXT_PRIVATE_UPLOAD_BUCKET'),
|
||||
Key: key,
|
||||
}),
|
||||
);
|
||||
@ -127,17 +132,16 @@ const getS3Client = () => {
|
||||
}
|
||||
|
||||
const hasCredentials =
|
||||
process.env.NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID &&
|
||||
process.env.NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY;
|
||||
env('NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID') && env('NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY');
|
||||
|
||||
return new S3Client({
|
||||
endpoint: process.env.NEXT_PRIVATE_UPLOAD_ENDPOINT || undefined,
|
||||
forcePathStyle: process.env.NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE === 'true',
|
||||
region: process.env.NEXT_PRIVATE_UPLOAD_REGION || 'us-east-1',
|
||||
endpoint: env('NEXT_PRIVATE_UPLOAD_ENDPOINT') || undefined,
|
||||
forcePathStyle: env('NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE') === 'true',
|
||||
region: env('NEXT_PRIVATE_UPLOAD_REGION') || 'us-east-1',
|
||||
credentials: hasCredentials
|
||||
? {
|
||||
accessKeyId: String(process.env.NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID),
|
||||
secretAccessKey: String(process.env.NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY),
|
||||
accessKeyId: String(env('NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID')),
|
||||
secretAccessKey: String(env('NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY')),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { base64 } from '@scure/base';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DocumentDataType } from '@documenso/prisma/client';
|
||||
|
||||
export type UpdateFileOptions = {
|
||||
type: DocumentDataType;
|
||||
oldData: string;
|
||||
|
||||
Reference in New Issue
Block a user