124 lines
2.9 KiB
TypeScript
124 lines
2.9 KiB
TypeScript
import { storeUploadLocally } from "@/lib/media/local-storage"
|
|
import { storeUploadToS3 } from "@/lib/media/s3-storage"
|
|
|
|
export type MediaStorageProvider = "local" | "s3"
|
|
|
|
type StoreUploadParams = {
|
|
file: File
|
|
assetId: string
|
|
variant: string
|
|
fileRole: string
|
|
}
|
|
|
|
type StoredUpload = {
|
|
storageKey: string
|
|
provider: MediaStorageProvider
|
|
fallbackReason?: string
|
|
}
|
|
|
|
type S3LikeError = {
|
|
name?: unknown
|
|
message?: unknown
|
|
Code?: unknown
|
|
code?: unknown
|
|
$metadata?: {
|
|
httpStatusCode?: unknown
|
|
requestId?: unknown
|
|
}
|
|
}
|
|
|
|
function resolveTenantId(): string {
|
|
return process.env.CMS_MEDIA_STORAGE_TENANT_ID?.trim() || "default"
|
|
}
|
|
|
|
function describeS3Error(error: unknown): string {
|
|
if (!error || typeof error !== "object") {
|
|
return "Unknown S3 error"
|
|
}
|
|
|
|
const err = error as S3LikeError
|
|
const details: string[] = []
|
|
|
|
if (typeof err.name === "string" && err.name.length > 0) {
|
|
details.push(`name=${err.name}`)
|
|
}
|
|
|
|
if (typeof err.message === "string" && err.message.length > 0) {
|
|
details.push(`message=${err.message}`)
|
|
}
|
|
|
|
if (typeof err.Code === "string" && err.Code.length > 0) {
|
|
details.push(`code=${err.Code}`)
|
|
} else if (typeof err.code === "string" && err.code.length > 0) {
|
|
details.push(`code=${err.code}`)
|
|
}
|
|
|
|
const status = err.$metadata?.httpStatusCode
|
|
if (typeof status === "number") {
|
|
details.push(`status=${status}`)
|
|
}
|
|
|
|
const requestId = err.$metadata?.requestId
|
|
if (typeof requestId === "string" && requestId.length > 0) {
|
|
details.push(`requestId=${requestId}`)
|
|
}
|
|
|
|
return details.length > 0 ? details.join(", ") : "Unknown S3 error"
|
|
}
|
|
|
|
export function resolveMediaStorageProvider(raw: string | undefined): MediaStorageProvider {
|
|
if (raw?.toLowerCase() === "local") {
|
|
return "local"
|
|
}
|
|
|
|
return "s3"
|
|
}
|
|
|
|
export async function storeUpload(params: StoreUploadParams): Promise<StoredUpload> {
|
|
const provider = resolveMediaStorageProvider(process.env.CMS_MEDIA_STORAGE_PROVIDER)
|
|
const tenantId = resolveTenantId()
|
|
|
|
if (provider === "s3") {
|
|
try {
|
|
const stored = await storeUploadToS3({
|
|
file: params.file,
|
|
tenantId,
|
|
assetId: params.assetId,
|
|
fileRole: params.fileRole,
|
|
variant: params.variant,
|
|
})
|
|
return {
|
|
...stored,
|
|
provider,
|
|
}
|
|
} catch (error) {
|
|
const detail = describeS3Error(error)
|
|
const fallbackStored = await storeUploadLocally({
|
|
file: params.file,
|
|
tenantId,
|
|
assetId: params.assetId,
|
|
fileRole: params.fileRole,
|
|
variant: params.variant,
|
|
})
|
|
return {
|
|
...fallbackStored,
|
|
provider: "local",
|
|
fallbackReason: `S3 upload failed; file stored locally instead. ${detail}`,
|
|
}
|
|
}
|
|
}
|
|
|
|
const stored = await storeUploadLocally({
|
|
file: params.file,
|
|
tenantId,
|
|
assetId: params.assetId,
|
|
fileRole: params.fileRole,
|
|
variant: params.variant,
|
|
})
|
|
|
|
return {
|
|
...stored,
|
|
provider,
|
|
}
|
|
}
|