104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
import { DeleteObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
|
|
|
|
import { buildMediaStorageKey } from "@/lib/media/storage-key"
|
|
|
|
type StoreS3UploadParams = {
|
|
file: File
|
|
tenantId: string
|
|
assetId: string
|
|
fileRole: string
|
|
variant: string
|
|
}
|
|
|
|
type StoredUpload = {
|
|
storageKey: string
|
|
}
|
|
|
|
type S3Config = {
|
|
bucket: string
|
|
region: string
|
|
endpoint?: string
|
|
accessKeyId: string
|
|
secretAccessKey: string
|
|
forcePathStyle?: boolean
|
|
}
|
|
|
|
function parseBoolean(value: string | undefined): boolean {
|
|
return value?.toLowerCase() === "true"
|
|
}
|
|
|
|
export function resolveS3Config(): S3Config {
|
|
const bucket = process.env.CMS_MEDIA_S3_BUCKET?.trim()
|
|
const region = process.env.CMS_MEDIA_S3_REGION?.trim()
|
|
const accessKeyId = process.env.CMS_MEDIA_S3_ACCESS_KEY_ID?.trim()
|
|
const secretAccessKey = process.env.CMS_MEDIA_S3_SECRET_ACCESS_KEY?.trim()
|
|
const endpoint = process.env.CMS_MEDIA_S3_ENDPOINT?.trim() || undefined
|
|
|
|
if (!bucket || !region || !accessKeyId || !secretAccessKey) {
|
|
throw new Error(
|
|
"S3 storage selected but required env vars are missing: CMS_MEDIA_S3_BUCKET, CMS_MEDIA_S3_REGION, CMS_MEDIA_S3_ACCESS_KEY_ID, CMS_MEDIA_S3_SECRET_ACCESS_KEY",
|
|
)
|
|
}
|
|
|
|
return {
|
|
bucket,
|
|
region,
|
|
endpoint,
|
|
accessKeyId,
|
|
secretAccessKey,
|
|
forcePathStyle: parseBoolean(process.env.CMS_MEDIA_S3_FORCE_PATH_STYLE),
|
|
}
|
|
}
|
|
|
|
export function createS3Client(config: S3Config): S3Client {
|
|
return new S3Client({
|
|
region: config.region,
|
|
endpoint: config.endpoint,
|
|
forcePathStyle: config.forcePathStyle,
|
|
credentials: {
|
|
accessKeyId: config.accessKeyId,
|
|
secretAccessKey: config.secretAccessKey,
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function storeUploadToS3(params: StoreS3UploadParams): Promise<StoredUpload> {
|
|
const config = resolveS3Config()
|
|
const client = createS3Client(config)
|
|
const storageKey = buildMediaStorageKey({
|
|
tenantId: params.tenantId,
|
|
assetId: params.assetId,
|
|
fileRole: params.fileRole,
|
|
variant: params.variant,
|
|
fileName: params.file.name,
|
|
})
|
|
const payload = new Uint8Array(await params.file.arrayBuffer())
|
|
|
|
await client.send(
|
|
new PutObjectCommand({
|
|
Bucket: config.bucket,
|
|
Key: storageKey,
|
|
Body: payload,
|
|
ContentType: params.file.type || undefined,
|
|
ContentLength: params.file.size,
|
|
CacheControl: "public, max-age=31536000, immutable",
|
|
}),
|
|
)
|
|
|
|
return { storageKey }
|
|
}
|
|
|
|
export async function deleteS3Object(storageKey: string): Promise<boolean> {
|
|
const config = resolveS3Config()
|
|
const client = createS3Client(config)
|
|
|
|
await client.send(
|
|
new DeleteObjectCommand({
|
|
Bucket: config.bucket,
|
|
Key: storageKey,
|
|
}),
|
|
)
|
|
|
|
return true
|
|
}
|