Files
music-v2/server/utils/object-storage.ts
T

102 lines
3.5 KiB
TypeScript

import { DeleteObjectCommand, DeleteObjectsCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
import type { H3Event } from "h3"
import { getBackendRuntimeConfig } from "./backend-config"
function getStorage(event: H3Event) {
const config = getBackendRuntimeConfig(event)
if (!config.s3Endpoint || !config.s3Bucket || !config.s3AccessKeyId || !config.s3SecretAccessKey) {
throw new Error("Configure NUXT_S3_ENDPOINT, NUXT_S3_BUCKET, NUXT_S3_ACCESS_KEY_ID, and NUXT_S3_SECRET_ACCESS_KEY")
}
const client = new S3Client({
endpoint: config.s3Endpoint,
region: config.s3Region,
forcePathStyle: true,
credentials: { accessKeyId: config.s3AccessKeyId, secretAccessKey: config.s3SecretAccessKey },
})
return { client, bucket: config.s3Bucket }
}
export type StoredObject = { key: string; body: Buffer; contentType: string }
export async function putObjects(event: H3Event, objects: StoredObject[]) {
const { client, bucket } = getStorage(event)
const uploaded: string[] = []
try {
for (const object of objects) {
await client.send(new PutObjectCommand({
Bucket: bucket,
Key: object.key,
Body: object.body,
ContentType: object.contentType || "application/octet-stream",
}))
uploaded.push(object.key)
}
return uploaded
} catch (error) {
await deleteObjects(event, uploaded).catch((cleanupError) => console.error("Unable to clean up partial bucket upload", cleanupError))
throw error
} finally {
client.destroy()
}
}
export async function deleteObjects(event: H3Event, keys: string[]) {
if (!keys.length) return
const { client, bucket } = getStorage(event)
try {
for (let index = 0; index < keys.length; index += 1000) {
const batch = keys.slice(index, index + 1000)
const deleted = await client.send(new DeleteObjectsCommand({
Bucket: bucket,
Delete: { Objects: batch.map((Key) => ({ Key })) },
}))
if (deleted.Errors?.length) throw new Error(`Unable to delete ${deleted.Errors.length} bucket object(s)`)
}
} finally {
client.destroy()
}
}
export async function deleteObject(event: H3Event, key: string) {
const { client, bucket } = getStorage(event)
try {
await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))
} finally {
client.destroy()
}
}
export async function deleteObjectPrefix(event: H3Event, prefix: string) {
const { client, bucket } = getStorage(event)
try {
let continuationToken: string | undefined
do {
const listed = await client.send(new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix, ContinuationToken: continuationToken }))
const keys = listed.Contents?.flatMap((object) => object.Key ? [object.Key] : []) ?? []
if (keys.length) {
const deleted = await client.send(new DeleteObjectsCommand({ Bucket: bucket, Delete: { Objects: keys.map((Key) => ({ Key })) } }))
if (deleted.Errors?.length) throw new Error(`Unable to delete ${deleted.Errors.length} bucket object(s)`)
}
continuationToken = listed.IsTruncated ? listed.NextContinuationToken : undefined
} while (continuationToken)
} finally {
client.destroy()
}
}
export async function tryDeleteObject(event: H3Event, key: string) {
try {
await deleteObject(event, key)
} catch (error) {
console.error("Unable to delete bucket object", key, error)
}
}
export async function tryDeleteObjectPrefix(event: H3Event, prefix: string) {
try {
await deleteObjectPrefix(event, prefix)
} catch (error) {
console.error("Unable to delete bucket object prefix", prefix, error)
}
}