initial commit
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user