mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
245 lines
5.6 KiB
TypeScript
245 lines
5.6 KiB
TypeScript
import "dotenv/config";
|
|
import { readFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { Readable } from "node:stream";
|
|
import { S3 } from "@aws-sdk/client-s3";
|
|
import { Upload } from "@aws-sdk/lib-storage";
|
|
import { logger } from "~/utils/logger";
|
|
import {
|
|
getArtFilename,
|
|
SEED_ART_URLS,
|
|
SEED_TEAM_IMAGES,
|
|
SEED_TOURNAMENT_IMAGES,
|
|
} from "./seed-art-urls";
|
|
|
|
async function checkMinioConnection(): Promise<boolean> {
|
|
try {
|
|
const {
|
|
STORAGE_END_POINT,
|
|
STORAGE_ACCESS_KEY,
|
|
STORAGE_SECRET,
|
|
STORAGE_REGION,
|
|
STORAGE_BUCKET,
|
|
} = process.env;
|
|
|
|
if (
|
|
!(
|
|
STORAGE_ACCESS_KEY &&
|
|
STORAGE_END_POINT &&
|
|
STORAGE_SECRET &&
|
|
STORAGE_REGION &&
|
|
STORAGE_BUCKET
|
|
)
|
|
) {
|
|
logger.warn("Storage configuration not found in environment");
|
|
return false;
|
|
}
|
|
|
|
const s3 = new S3({
|
|
endpoint: STORAGE_END_POINT,
|
|
forcePathStyle: false,
|
|
credentials: {
|
|
accessKeyId: STORAGE_ACCESS_KEY,
|
|
secretAccessKey: STORAGE_SECRET,
|
|
},
|
|
region: STORAGE_REGION,
|
|
});
|
|
|
|
await s3.headBucket({ Bucket: STORAGE_BUCKET });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function downloadImage(url: string): Promise<Buffer> {
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to download image: ${response.statusText}`);
|
|
}
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
return Buffer.from(arrayBuffer);
|
|
}
|
|
|
|
async function readLocalImage(filename: string): Promise<Buffer> {
|
|
const imagePath = join(process.cwd(), "app", "db", "seed", "img", filename);
|
|
return await readFile(imagePath);
|
|
}
|
|
|
|
async function uploadToMinio(
|
|
imageBuffer: Buffer,
|
|
filename: string,
|
|
): Promise<string> {
|
|
const {
|
|
STORAGE_END_POINT,
|
|
STORAGE_ACCESS_KEY,
|
|
STORAGE_SECRET,
|
|
STORAGE_REGION,
|
|
STORAGE_BUCKET,
|
|
} = process.env;
|
|
|
|
const s3 = new S3({
|
|
endpoint: STORAGE_END_POINT,
|
|
forcePathStyle: false,
|
|
credentials: {
|
|
accessKeyId: STORAGE_ACCESS_KEY!,
|
|
secretAccessKey: STORAGE_SECRET!,
|
|
},
|
|
region: STORAGE_REGION,
|
|
});
|
|
|
|
const stream = Readable.from(imageBuffer);
|
|
|
|
const upload = new Upload({
|
|
client: s3,
|
|
params: {
|
|
Bucket: STORAGE_BUCKET!,
|
|
Key: filename,
|
|
Body: stream,
|
|
ACL: "public-read",
|
|
},
|
|
});
|
|
|
|
await upload.done();
|
|
|
|
return filename;
|
|
}
|
|
|
|
async function fileExistsInMinio(filename: string): Promise<boolean> {
|
|
try {
|
|
const {
|
|
STORAGE_END_POINT,
|
|
STORAGE_ACCESS_KEY,
|
|
STORAGE_SECRET,
|
|
STORAGE_REGION,
|
|
STORAGE_BUCKET,
|
|
} = process.env;
|
|
|
|
const s3 = new S3({
|
|
endpoint: STORAGE_END_POINT,
|
|
forcePathStyle: false,
|
|
credentials: {
|
|
accessKeyId: STORAGE_ACCESS_KEY!,
|
|
secretAccessKey: STORAGE_SECRET!,
|
|
},
|
|
region: STORAGE_REGION,
|
|
});
|
|
|
|
await s3.headObject({
|
|
Bucket: STORAGE_BUCKET!,
|
|
Key: filename,
|
|
});
|
|
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function seedImages(): Promise<void> {
|
|
const minioAvailable = await checkMinioConnection();
|
|
|
|
if (!minioAvailable) {
|
|
logger.warn(
|
|
"⚠️ Minio is not available. Skipping image seeding. Make sure Docker is running if you want to seed images.",
|
|
);
|
|
return;
|
|
}
|
|
|
|
logger.info(`📥 Processing ${SEED_ART_URLS.length} art images`);
|
|
|
|
let successCount = 0;
|
|
let failCount = 0;
|
|
let skippedCount = 0;
|
|
|
|
for (let i = 0; i < SEED_ART_URLS.length; i++) {
|
|
const url = SEED_ART_URLS[i];
|
|
const filename = getArtFilename(i);
|
|
const smallFilename = filename.replace(/\.(\w+)$/, "-small.$1");
|
|
|
|
try {
|
|
const regularExists = await fileExistsInMinio(filename);
|
|
const smallExists = await fileExistsInMinio(smallFilename);
|
|
|
|
if (regularExists && smallExists) {
|
|
skippedCount++;
|
|
logger.info(
|
|
` ↷ Files ${filename} and ${smallFilename} already exist in Minio`,
|
|
);
|
|
} else {
|
|
const imageBuffer = await downloadImage(url);
|
|
|
|
if (!regularExists) {
|
|
logger.info(` Uploading ${filename} to Minio...`);
|
|
await uploadToMinio(imageBuffer, filename);
|
|
}
|
|
|
|
if (!smallExists) {
|
|
logger.info(` Uploading ${smallFilename} to Minio...`);
|
|
await uploadToMinio(imageBuffer, smallFilename);
|
|
}
|
|
|
|
successCount++;
|
|
}
|
|
} catch (err) {
|
|
failCount++;
|
|
logger.error(
|
|
` ✗ Failed to process ${filename}: ${(err as Error).message}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
logger.info(
|
|
`\n✅ Art image seeding complete: ${successCount} uploaded, ${skippedCount} already existed, ${failCount} failed`,
|
|
);
|
|
|
|
logger.info(
|
|
`\n📥 Processing ${SEED_TEAM_IMAGES.length} team images and ${SEED_TOURNAMENT_IMAGES.length} tournament images`,
|
|
);
|
|
|
|
const localImages = [...SEED_TEAM_IMAGES, ...SEED_TOURNAMENT_IMAGES];
|
|
|
|
let localSuccessCount = 0;
|
|
let localFailCount = 0;
|
|
let localSkippedCount = 0;
|
|
|
|
for (const { filename } of localImages) {
|
|
const smallFilename = filename.replace(/\.(\w+)$/, "-small.$1");
|
|
|
|
try {
|
|
const regularExists = await fileExistsInMinio(filename);
|
|
const smallExists = await fileExistsInMinio(smallFilename);
|
|
|
|
if (regularExists && smallExists) {
|
|
localSkippedCount++;
|
|
logger.info(
|
|
` ↷ Files ${filename} and ${smallFilename} already exist in Minio`,
|
|
);
|
|
} else {
|
|
const imageBuffer = await readLocalImage(filename);
|
|
|
|
if (!regularExists) {
|
|
logger.info(` Uploading ${filename} to Minio...`);
|
|
await uploadToMinio(imageBuffer, filename);
|
|
}
|
|
|
|
if (!smallExists) {
|
|
logger.info(` Uploading ${smallFilename} to Minio...`);
|
|
await uploadToMinio(imageBuffer, smallFilename);
|
|
}
|
|
|
|
localSuccessCount++;
|
|
}
|
|
} catch (err) {
|
|
localFailCount++;
|
|
logger.error(
|
|
` ✗ Failed to process ${filename}: ${(err as Error).message}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
logger.info(
|
|
`\n✅ Local image seeding complete: ${localSuccessCount} uploaded, ${localSkippedCount} already existed, ${localFailCount} failed`,
|
|
);
|
|
}
|