Add S3 storage configuration and client
This commit is contained in:
1 parent
39cb48a656
commit
c2dcec2c31
7 files changed
+575
-7
No files matched your search
@@ -10,7 +10,11 @@ export function getBackendRuntimeConfig(event?: H3Event) {
|
||||
|
||||
return {
|
||||
mongodbUri: config.mongodbUri || process.env.MONGODB_URI || "",
|
||||
uploadDir: config.uploadDir || process.env.UPLOAD_DIR || "",
|
||||
tokenExpire: config.tokenExpire || process.env.TOKEN_EXPIRE || "1h",
|
||||
s3Endpoint: config.s3Endpoint || process.env.S3_ENDPOINT || "",
|
||||
s3Bucket: config.s3Bucket || process.env.S3_BUCKET || "",
|
||||
s3Region: config.s3Region || process.env.S3_REGION || "us-east-1",
|
||||
s3AccessKeyId: config.s3AccessKeyId || process.env.S3_ACCESS_KEY_ID || "",
|
||||
s3SecretAccessKey: config.s3SecretAccessKey || process.env.S3_SECRET_ACCESS_KEY || "",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user