Files
2026-05-13 14:20:41 +00:00

173 lines
4.9 KiB
JavaScript

import {
S3Client,
PutObjectCommand,
GetObjectCommand,
DeleteObjectCommand,
CreateMultipartUploadCommand,
UploadPartCommand,
CompleteMultipartUploadCommand,
AbortMultipartUploadCommand,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { env } from '../config/env.js';
// S3 client for internal server-to-server communication
const s3Client = new S3Client({
endpoint: env.S3_ENDPOINT,
credentials: {
accessKeyId: env.S3_ACCESS_KEY,
secretAccessKey: env.S3_SECRET_KEY,
},
region: 'us-east-1',
forcePathStyle: true,
// Оптимизация для больших файлов
requestHandler: {
connectionTimeout: 300000, // 5 минут
socketTimeout: 600000, // 10 минут
},
});
// S3 client for generating presigned URLs for browser access
// Uses the same credentials but will generate URLs with public endpoint
const s3ClientForPresigned = new S3Client({
endpoint: env.S3_ENDPOINT,
credentials: {
accessKeyId: env.S3_ACCESS_KEY,
secretAccessKey: env.S3_SECRET_KEY,
},
region: 'us-east-1',
forcePathStyle: true,
});
export async function uploadFile(key, body, contentType) {
const command = new PutObjectCommand({
Bucket: env.S3_BUCKET,
Key: key,
Body: body,
ContentType: contentType,
});
await s3Client.send(command);
return key;
}
export async function getPresignedUrl(key, expiresIn = env.S3_PRESIGNED_URL_EXPIRES_IN) {
const command = new GetObjectCommand({
Bucket: env.S3_BUCKET,
Key: key,
});
const signedUrl = await getSignedUrl(s3Client, command, { expiresIn });
return signedUrl;
}
export async function deleteFile(key) {
const command = new DeleteObjectCommand({
Bucket: env.S3_BUCKET,
Key: key,
});
await s3Client.send(command);
return true;
}
export function getImageInputKey(filename, userId, folder = env.S3_IMAGES_INPUT_FOLDER) {
const timestamp = Date.now();
const ext = filename.split('.').pop() || 'jpg';
return `${folder}/${userId}/${timestamp}-${filename}`;
}
export function getVideoInputKey(filename, userId, folder = 'videos_input') {
const timestamp = Date.now();
const ext = filename.split('.').pop() || 'mp4';
return `${folder}/${userId}/${timestamp}-${filename}`;
}
// ──────────────────────────────
// Multipart Upload для прямой загрузки в S3
// ──────────────────────────────
/**
* Инициировать multipart upload
*/
export async function createMultipartUpload(key, contentType) {
const command = new CreateMultipartUploadCommand({
Bucket: env.S3_BUCKET,
Key: key,
ContentType: contentType,
});
const result = await s3Client.send(command);
return {
uploadId: result.UploadId,
key,
};
}
/**
* Создать presigned URL для загрузки части файла
* Возвращает URL для прямой загрузки из браузера через nginx proxy
*/
export async function getPresignedPartUrl(key, uploadId, partNumber, expiresIn = 3600) {
const command = new UploadPartCommand({
Bucket: env.S3_BUCKET,
Key: key,
UploadId: uploadId,
PartNumber: partNumber,
});
// Для presigned URL используем endpoint который совпадает с public endpoint
// Браузер будет отправлять запрос на https://uno-click.pip-test.ru/s3-upload/uno-click/
// Нginx будет проксировать на minio:9000/uno-click/
// MinIO проверяет подпись используя только путь и query параметры, не host
const signedUrl = await getSignedUrl(s3ClientForPresigned, command, { expiresIn });
// Заменяем внутренний endpoint на публичный для доступа из браузера
// Превращаем http://minio:9000/uno-click/... в https://uno-click.pip-test.ru/s3-upload/uno-click/...
const publicUrl = signedUrl.replace(
`${env.S3_ENDPOINT}/${env.S3_BUCKET}/`,
env.S3_PUBLIC_ENDPOINT
);
return publicUrl;
}
/**
* Завершить multipart upload
*/
export async function completeMultipartUpload(key, uploadId, parts) {
const command = new CompleteMultipartUploadCommand({
Bucket: env.S3_BUCKET,
Key: key,
UploadId: uploadId,
MultipartUpload: {
Parts: parts.map((p, index) => ({
PartNumber: index + 1,
ETag: p.ETag,
})),
},
});
const result = await s3Client.send(command);
return {
key,
location: result.Location,
bucket: result.Bucket,
etag: result.ETag,
};
}
/**
* Отменить multipart upload
*/
export async function abortMultipartUpload(key, uploadId) {
const command = new AbortMultipartUploadCommand({
Bucket: env.S3_BUCKET,
Key: key,
UploadId: uploadId,
});
await s3Client.send(command);
return true;
}