feat(media): default to s3 with local upload fallback

This commit is contained in:
2026-02-12 18:16:11 +01:00
parent 19738b77d8
commit 86a8af25d8
4 changed files with 28 additions and 14 deletions

View File

@@ -10,11 +10,11 @@ CMS_SUPPORT_EMAIL="support@cms.local"
CMS_SUPPORT_PASSWORD="change-me-support-password" CMS_SUPPORT_PASSWORD="change-me-support-password"
CMS_SUPPORT_NAME="Technical Support" CMS_SUPPORT_NAME="Technical Support"
CMS_SUPPORT_LOGIN_KEY="support-access-change-me" CMS_SUPPORT_LOGIN_KEY="support-access-change-me"
CMS_MEDIA_STORAGE_PROVIDER="local" CMS_MEDIA_STORAGE_PROVIDER="s3"
CMS_MEDIA_UPLOAD_MAX_BYTES="26214400" CMS_MEDIA_UPLOAD_MAX_BYTES="26214400"
# Optional: override local media storage directory for admin upload adapter. # Optional: override local media storage directory for admin upload adapter.
# CMS_MEDIA_LOCAL_STORAGE_DIR="/absolute/path/to/media-storage" # CMS_MEDIA_LOCAL_STORAGE_DIR="/absolute/path/to/media-storage"
# S3/object-storage config (required when CMS_MEDIA_STORAGE_PROVIDER="s3"). # S3/object-storage config (default provider). If unavailable, upload falls back to local storage.
# CMS_MEDIA_S3_BUCKET="cms-media" # CMS_MEDIA_S3_BUCKET="cms-media"
# CMS_MEDIA_S3_REGION="eu-central-1" # CMS_MEDIA_S3_REGION="eu-central-1"
# CMS_MEDIA_S3_ACCESS_KEY_ID="" # CMS_MEDIA_S3_ACCESS_KEY_ID=""

View File

@@ -81,7 +81,8 @@ export default async function MediaManagementPage({
<h2 className="text-xl font-medium">Upload Media Asset</h2> <h2 className="text-xl font-medium">Upload Media Asset</h2>
<p className="mt-1 text-sm text-neutral-600"> <p className="mt-1 text-sm text-neutral-600">
Upload storage provider: <strong>{activeStorageProvider}</strong>. You can switch via Upload storage provider: <strong>{activeStorageProvider}</strong>. You can switch via
`CMS_MEDIA_STORAGE_PROVIDER` (`local` or `s3`) until the admin settings toggle lands. `CMS_MEDIA_STORAGE_PROVIDER` (`s3` default, `local` fallback) until the admin settings
toggle lands.
</p> </p>
<MediaUploadForm /> <MediaUploadForm />
</section> </section>

View File

@@ -3,8 +3,8 @@ import { describe, expect, it } from "vitest"
import { resolveMediaStorageProvider } from "@/lib/media/storage" import { resolveMediaStorageProvider } from "@/lib/media/storage"
describe("resolveMediaStorageProvider", () => { describe("resolveMediaStorageProvider", () => {
it("defaults to local when unset", () => { it("defaults to s3 when unset", () => {
expect(resolveMediaStorageProvider(undefined)).toBe("local") expect(resolveMediaStorageProvider(undefined)).toBe("s3")
}) })
it("resolves s3", () => { it("resolves s3", () => {
@@ -12,7 +12,12 @@ describe("resolveMediaStorageProvider", () => {
expect(resolveMediaStorageProvider("S3")).toBe("s3") expect(resolveMediaStorageProvider("S3")).toBe("s3")
}) })
it("falls back to local for unknown values", () => { it("resolves local explicitly", () => {
expect(resolveMediaStorageProvider("foo")).toBe("local") expect(resolveMediaStorageProvider("local")).toBe("local")
expect(resolveMediaStorageProvider("LOCAL")).toBe("local")
})
it("falls back to s3 for unknown values", () => {
expect(resolveMediaStorageProvider("foo")).toBe("s3")
}) })
}) })

View File

@@ -14,22 +14,30 @@ type StoredUpload = {
} }
export function resolveMediaStorageProvider(raw: string | undefined): MediaStorageProvider { export function resolveMediaStorageProvider(raw: string | undefined): MediaStorageProvider {
if (raw?.toLowerCase() === "s3") { if (raw?.toLowerCase() === "local") {
return "s3" return "local"
} }
return "local" return "s3"
} }
export async function storeUpload(params: StoreUploadParams): Promise<StoredUpload> { export async function storeUpload(params: StoreUploadParams): Promise<StoredUpload> {
const provider = resolveMediaStorageProvider(process.env.CMS_MEDIA_STORAGE_PROVIDER) const provider = resolveMediaStorageProvider(process.env.CMS_MEDIA_STORAGE_PROVIDER)
if (provider === "s3") { if (provider === "s3") {
try {
const stored = await storeUploadToS3(params) const stored = await storeUploadToS3(params)
return { return {
...stored, ...stored,
provider, provider,
} }
} catch {
const fallbackStored = await storeUploadLocally(params)
return {
...fallbackStored,
provider: "local",
}
}
} }
const stored = await storeUploadLocally(params) const stored = await storeUploadLocally(params)