diff --git a/.env.example b/.env.example index 2f722a67d2..e67e91976f 100644 --- a/.env.example +++ b/.env.example @@ -56,6 +56,19 @@ SMTP_PASSWORD=smtpPassword # Uncomment the variables you would like to use and customize the values. +############## +# S3 STORAGE # +############## + +# S3 Storage is required for the file uplaod in serverless environments like Vercel +S3_ACCESS_KEY= +S3_SECRET_KEY= +S3_REGION= +S3_BUCKET_NAME= +# Configure a third party S3 compatible storage service endpoint like StorJ leave empty if you use Amazon S3 +# e.g., https://gateway.storjshare.io +S3_ENDPOINT_URL= + ##################### # Disable Features # ##################### diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index 96810611d2..c19a465076 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -87,6 +87,7 @@ export const ONBOARDING_DISABLED = env.ONBOARDING_DISABLED; export const S3_ACCESS_KEY = env.S3_ACCESS_KEY; export const S3_SECRET_KEY = env.S3_SECRET_KEY; export const S3_REGION = env.S3_REGION; +export const S3_ENDPOINT_URL = env.S3_ENDPOINT_URL; export const S3_BUCKET_NAME = env.S3_BUCKET_NAME; export const UPLOADS_DIR = "./uploads"; export const MAX_SIZES = { diff --git a/packages/lib/env.ts b/packages/lib/env.ts index 00d1e7107a..04e3a3a0e5 100644 --- a/packages/lib/env.ts +++ b/packages/lib/env.ts @@ -61,6 +61,7 @@ export const env = createEnv({ S3_BUCKET_NAME: z.string().optional(), S3_REGION: z.string().optional(), S3_SECRET_KEY: z.string().optional(), + S3_ENDPOINT_URL: z.string().optional(), SHORT_URL_BASE: z.string().url().optional().or(z.string().length(0)), SIGNUP_DISABLED: z.enum(["1", "0"]).optional(), SMTP_HOST: z.string().min(1).optional(), @@ -156,6 +157,7 @@ export const env = createEnv({ S3_BUCKET_NAME: process.env.S3_BUCKET_NAME, S3_REGION: process.env.S3_REGION, S3_SECRET_KEY: process.env.S3_SECRET_KEY, + S3_ENDPOINT_URL: process.env.S3_ENDPOINT_URL, SHORT_URL_BASE: process.env.SHORT_URL_BASE, SIGNUP_DISABLED: process.env.SIGNUP_DISABLED, SMTP_HOST: process.env.SMTP_HOST, diff --git a/packages/lib/storage/service.ts b/packages/lib/storage/service.ts index 97cebcdfb9..317f514395 100644 --- a/packages/lib/storage/service.ts +++ b/packages/lib/storage/service.ts @@ -20,8 +20,11 @@ import { TAccessType } from "@formbricks/types/storage"; import { IS_S3_CONFIGURED, MAX_SIZES, + S3_ACCESS_KEY, S3_BUCKET_NAME, + S3_ENDPOINT_URL, S3_REGION, + S3_SECRET_KEY, UPLOADS_DIR, WEBAPP_URL, } from "../constants"; @@ -30,14 +33,21 @@ import { env } from "../env"; import { storageCache } from "./cache"; // S3Client Singleton +let s3ClientInstance: S3Client | null = null; -export const s3Client = new S3Client({ - credentials: { - accessKeyId: env.S3_ACCESS_KEY!, - secretAccessKey: env.S3_SECRET_KEY!, - }, - region: S3_REGION!, -}); +export const getS3Client = () => { + if (!s3ClientInstance) { + s3ClientInstance = new S3Client({ + credentials: { + accessKeyId: S3_ACCESS_KEY!, + secretAccessKey: S3_SECRET_KEY!, + }, + region: S3_REGION, + endpoint: S3_ENDPOINT_URL, + }); + } + return s3ClientInstance; +}; const ensureDirectoryExists = async (dirPath: string) => { try { @@ -85,6 +95,7 @@ const getS3SignedUrl = async (fileKey: string): Promise => { }); try { + const s3Client = getS3Client(); return await getSignedUrl(s3Client, getObjectCommand, { expiresIn }); } catch (err) { throw err; @@ -239,6 +250,7 @@ export const getS3UploadSignedUrl = async ( const postConditions: PresignedPostOptions["Conditions"] = [["content-length-range", 0, maxSize]]; try { + const s3Client = getS3Client(); const { fields, url } = await createPresignedPost(s3Client, { Expires: 10 * 60, // 10 minutes Bucket: env.S3_BUCKET_NAME!, @@ -309,6 +321,7 @@ export const putFile = async ( }; const command = new PutObjectCommand(input); + const s3Client = getS3Client(); await s3Client.send(command); return { success: true, message: "File uploaded" }; } @@ -358,6 +371,7 @@ export const deleteS3File = async (fileKey: string) => { }); try { + const s3Client = getS3Client(); await s3Client.send(deleteObjectCommand); } catch (err) { throw err; @@ -367,6 +381,7 @@ export const deleteS3File = async (fileKey: string) => { export const deleteS3FilesByEnvironmentId = async (environmentId: string) => { try { // List all objects in the bucket with the prefix of environmentId + const s3Client = getS3Client(); const listObjectsOutput = await s3Client.send( new ListObjectsCommand({ Bucket: S3_BUCKET_NAME, diff --git a/turbo.json b/turbo.json index 51cfba1ce9..ab63374c6a 100644 --- a/turbo.json +++ b/turbo.json @@ -119,6 +119,7 @@ "S3_SECRET_KEY", "S3_REGION", "S3_BUCKET_NAME", + "S3_ENDPOINT_URL", "SENTRY_DSN", "SHORT_URL_BASE", "SIGNUP_DISABLED",