From 9c450a3d07b65ab3f421e69693dc0ee9fe9f62c2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 27 May 2026 11:01:21 +0800 Subject: [PATCH] fix: make direct upload cors script standalone --- .../storage/configure-direct-upload-cors.ts | 128 ++++++++++++++++-- 1 file changed, 117 insertions(+), 11 deletions(-) diff --git a/scripts/storage/configure-direct-upload-cors.ts b/scripts/storage/configure-direct-upload-cors.ts index 37217879f..37343d434 100644 --- a/scripts/storage/configure-direct-upload-cors.ts +++ b/scripts/storage/configure-direct-upload-cors.ts @@ -3,13 +3,21 @@ import "dotenv/config"; import { GetBucketCorsCommand, PutBucketCorsCommand, + S3Client, type CORSRule, } from "@aws-sdk/client-s3"; +import { and, eq } from "drizzle-orm"; -import { - createS3Client, - resolveS3UploadTarget, -} from "@/lib/storage/service"; +import { db } from "@/lib/db"; +import { storageConfig } from "@/lib/db/schema"; + +type S3UploadConfig = { + endpoint: string; + region: string; + bucket: string; + accessKeyId: string; + secretAccessKey: string; +}; const DEFAULT_ORIGINS = [ "https://ai.yydd.ai", @@ -20,6 +28,103 @@ const DEFAULT_ORIGINS = [ "http://localhost:3001", ]; +function normalizeEndpoint(endpoint: string): string { + const trimmed = endpoint.trim().replace(/\/+$/, ""); + if (!trimmed) return ""; + + if (/aliyuncs\.com/i.test(trimmed)) { + const withoutProtocol = trimmed.replace(/^https?:\/\//i, ""); + return `https://${withoutProtocol}`; + } + + return trimmed; +} + +function createS3Client(config: S3UploadConfig) { + const normalizedEndpoint = normalizeEndpoint(config.endpoint); + const lowerEndpoint = normalizedEndpoint.toLowerCase(); + const isCloudS3 = + lowerEndpoint.includes("aliyuncs.com") || + lowerEndpoint.includes("amazonaws.com"); + + return new S3Client({ + endpoint: normalizedEndpoint, + region: config.region || "auto", + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + forcePathStyle: !isCloudS3, + }); +} + +function toS3Config(value: unknown): S3UploadConfig | null { + const raw = value as Record | null | undefined; + const endpoint = raw?.endpoint ? normalizeEndpoint(String(raw.endpoint)) : ""; + const bucket = raw?.bucket ? String(raw.bucket).trim() : ""; + const accessKeyId = raw?.accessKeyId ? String(raw.accessKeyId).trim() : ""; + const secretAccessKey = raw?.secretAccessKey + ? String(raw.secretAccessKey).trim() + : ""; + + if ( + !raw || + raw.type !== "s3" || + raw.isEnabled !== true || + !endpoint || + !bucket || + !accessKeyId || + !secretAccessKey + ) { + return null; + } + + return { + endpoint, + region: String(raw.region || "auto").trim() || "auto", + bucket, + accessKeyId, + secretAccessKey, + }; +} + +function readEnvS3Config(): S3UploadConfig | null { + const endpoint = process.env.S3_ENDPOINT + ? normalizeEndpoint(process.env.S3_ENDPOINT) + : ""; + const bucket = process.env.S3_BUCKET?.trim() || ""; + const accessKeyId = process.env.S3_ACCESS_KEY_ID?.trim() || ""; + const secretAccessKey = process.env.S3_SECRET_ACCESS_KEY?.trim() || ""; + + if (!endpoint || !bucket || !accessKeyId || !secretAccessKey) { + return null; + } + + return { + endpoint, + region: process.env.S3_REGION?.trim() || "auto", + bucket, + accessKeyId, + secretAccessKey, + }; +} + +async function resolveS3Config() { + const [defaultConfig] = await db + .select() + .from(storageConfig) + .where( + and( + eq(storageConfig.type, "s3"), + eq(storageConfig.isDefault, true), + eq(storageConfig.isEnabled, true) + ) + ) + .limit(1); + + return toS3Config(defaultConfig) || readEnvS3Config(); +} + function readOrigins() { const raw = process.env.DIRECT_UPLOAD_CORS_ORIGINS || ""; const origins = raw @@ -51,15 +156,14 @@ function isDirectUploadRule(rule: CORSRule, origins: string[]) { } async function main() { - const target = await resolveS3UploadTarget(); - if (!target) { + const config = await resolveS3Config(); + if (!config) { console.log("[direct-upload-cors] skip: no S3-compatible upload target"); return; } const origins = readOrigins(); - const client = createS3Client(target.config); - const bucket = target.config.bucket; + const client = createS3Client(config); const directRule: CORSRule = { AllowedOrigins: origins, AllowedMethods: ["GET", "PUT", "POST", "HEAD"], @@ -70,7 +174,9 @@ async function main() { let existingRules: CORSRule[] = []; try { - const current = await client.send(new GetBucketCorsCommand({ Bucket: bucket })); + const current = await client.send( + new GetBucketCorsCommand({ Bucket: config.bucket }) + ); existingRules = current.CORSRules || []; } catch (error) { console.warn( @@ -88,7 +194,7 @@ async function main() { await client.send( new PutBucketCorsCommand({ - Bucket: bucket, + Bucket: config.bucket, CORSConfiguration: { CORSRules: mergedRules, }, @@ -96,7 +202,7 @@ async function main() { ); console.log("[direct-upload-cors] configured", { - bucket, + bucket: config.bucket, origins, ruleCount: mergedRules.length, });