diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index 45678b7d..e4f00149 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -90,6 +90,7 @@ export const env = createEnv({ NEXT_PUBLIC_BASE_URL: z.string().url(), NEXT_PUBLIC_STORAGE_URL: z.string().url().optional(), NEXT_PUBLIC_AVATAR_BUCKET_NAME: z.string().optional(), + NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME: z.string().optional(), NEXT_PUBLIC_STORAGE_DOMAIN: z.string().optional(), NEXT_PUBLIC_ALLOW_CREDENTIALS: z .string() @@ -125,6 +126,8 @@ export const env = createEnv({ NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL, NEXT_PUBLIC_STORAGE_URL: process.env.NEXT_PUBLIC_STORAGE_URL, NEXT_PUBLIC_AVATAR_BUCKET_NAME: process.env.NEXT_PUBLIC_AVATAR_BUCKET_NAME, + NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME: + process.env.NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME, NEXT_PUBLIC_STORAGE_DOMAIN: process.env.NEXT_PUBLIC_STORAGE_DOMAIN, NEXT_PUBLIC_ALLOW_CREDENTIALS: process.env.NEXT_PUBLIC_ALLOW_CREDENTIALS, NEXT_PUBLIC_DISABLE_SIGN_UP: process.env.NEXT_PUBLIC_DISABLE_SIGN_UP, diff --git a/packages/api/package.json b/packages/api/package.json index df31bb81..7c6e3a0e 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -35,6 +35,8 @@ "typecheck": "tsc --noEmit --emitDeclarationOnly false" }, "dependencies": { + "@aws-sdk/client-s3": "^3.802.0", + "@aws-sdk/s3-request-presigner": "^3.812.0", "@kan/auth": "workspace:*", "@kan/db": "workspace:*", "@kan/email": "workspace:^", diff --git a/packages/api/src/utils/s3.ts b/packages/api/src/utils/s3.ts new file mode 100644 index 00000000..961bb33c --- /dev/null +++ b/packages/api/src/utils/s3.ts @@ -0,0 +1,53 @@ +import { + GetObjectCommand, + PutObjectCommand, + S3Client, +} from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + +export function createS3Client() { + return new S3Client({ + region: process.env.S3_REGION ?? "", + endpoint: process.env.S3_ENDPOINT ?? "", + forcePathStyle: process.env.S3_FORCE_PATH_STYLE === "true", + credentials: { + accessKeyId: process.env.S3_ACCESS_KEY_ID ?? "", + secretAccessKey: process.env.S3_SECRET_ACCESS_KEY ?? "", + }, + }); +} + +export async function generateUploadUrl( + bucket: string, + key: string, + contentType: string, + expiresIn = 3600, +) { + const client = createS3Client(); + return getSignedUrl( + client, + new PutObjectCommand({ + Bucket: bucket, + Key: key, + ContentType: contentType, + // Don't set ACL for private files + }), + { expiresIn }, + ); +} + +export async function generateDownloadUrl( + bucket: string, + key: string, + expiresIn = 3600, +) { + const client = createS3Client(); + return getSignedUrl( + client, + new GetObjectCommand({ + Bucket: bucket, + Key: key, + }), + { expiresIn }, + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8478c667..2c9d2f0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -293,6 +293,12 @@ importers: packages/api: dependencies: + '@aws-sdk/client-s3': + specifier: ^3.802.0 + version: 3.879.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.812.0 + version: 3.879.0 '@kan/auth': specifier: workspace:* version: link:../auth