Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m34s
107 lines
4.1 KiB
JavaScript
107 lines
4.1 KiB
JavaScript
/**
|
|
* Универсальные хелперы для работы с S3
|
|
* Устраняют дублирование кода в routes
|
|
*/
|
|
|
|
const { s3, BUCKET_NAME } = require('../services/s3Service');
|
|
const { GetObjectCommand, HeadObjectCommand } = require('@aws-sdk/client-s3');
|
|
const { streamToString } = require('../services/s3Service');
|
|
const { sendError, checkIfNoneMatch } = require('../middleware/errorHandler');
|
|
|
|
/**
|
|
* Универсальный GET для JSON объектов из S3 с заголовками
|
|
* @param {string} s3Key - Ключ S3
|
|
* @param {object} req - Express request
|
|
* @param {object} res - Express response
|
|
* @param {object} options - Опции
|
|
* @param {function} options.transform - Функция трансформации данных
|
|
* @param {*} options.defaultValue - Значение по умолчанию при отсутствии файла
|
|
*/
|
|
async function getS3JsonWithHeaders(s3Key, req, res, options = {}) {
|
|
const { transform = (data) => data, defaultValue = [] } = options;
|
|
|
|
try {
|
|
// Получаем HEAD для установки заголовков
|
|
const head = await s3.send(new HeadObjectCommand({ Bucket: BUCKET_NAME, Key: s3Key })).catch(() => null);
|
|
const etag = head?.ETag || null;
|
|
|
|
// Устанавливаем заголовки
|
|
if (etag) res.set('ETag', String(etag));
|
|
if (head?.LastModified) res.set('Last-Modified', new Date(head.LastModified).toUTCString());
|
|
if (typeof head?.ContentLength === 'number') res.set('Content-Length-Source', String(head.ContentLength));
|
|
|
|
// Проверяем If-None-Match
|
|
if (checkIfNoneMatch(req, res, etag)) return;
|
|
|
|
// Получаем объект
|
|
const data = await s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: s3Key }));
|
|
const fileContent = await streamToString(data.Body);
|
|
|
|
let parsed;
|
|
try {
|
|
parsed = JSON.parse(fileContent);
|
|
if (!Array.isArray(parsed) && Array.isArray(defaultValue)) {
|
|
parsed = defaultValue;
|
|
}
|
|
} catch (parseError) {
|
|
console.error(`Error parsing ${s3Key}:`, parseError);
|
|
parsed = defaultValue;
|
|
}
|
|
|
|
// Применяем трансформацию
|
|
const result = transform(parsed);
|
|
|
|
res.json(result);
|
|
} catch (error) {
|
|
if (error?.code === 'NoSuchKey' || error?.$metadata?.httpStatusCode === 404) {
|
|
res.json(defaultValue);
|
|
} else {
|
|
console.error(error);
|
|
return sendError(res, 500, 'Error reading from S3', 'E_S3');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Универсальный GET для текстовых объектов из S3 с заголовками
|
|
* @param {string} s3Key - Ключ S3
|
|
* @param {object} req - Express request
|
|
* @param {object} res - Express response
|
|
* @param {object} options - Опции
|
|
* @param {function} options.transform - Функция трансформации текста
|
|
* @param {string} options.defaultValue - Значение по умолчанию при отсутствии файла
|
|
*/
|
|
async function getS3TextWithHeaders(s3Key, req, res, options = {}) {
|
|
const { transform = (text) => ({ config: text }), defaultValue = '' } = options;
|
|
|
|
try {
|
|
const head = await s3.send(new HeadObjectCommand({ Bucket: BUCKET_NAME, Key: s3Key })).catch(() => null);
|
|
const etag = head?.ETag || null;
|
|
|
|
if (etag) res.set('ETag', String(etag));
|
|
if (head?.LastModified) res.set('Last-Modified', new Date(head.LastModified).toUTCString());
|
|
if (typeof head?.ContentLength === 'number') res.set('Content-Length-Source', String(head.ContentLength));
|
|
|
|
if (checkIfNoneMatch(req, res, etag)) return;
|
|
|
|
const data = await s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: s3Key }));
|
|
const text = await streamToString(data.Body);
|
|
|
|
const result = transform(text);
|
|
res.json(result);
|
|
} catch (error) {
|
|
if (error?.code === 'NoSuchKey' || error?.$metadata?.httpStatusCode === 404) {
|
|
res.json(transform(defaultValue));
|
|
} else {
|
|
console.error(error);
|
|
return sendError(res, 500, 'Error reading from S3', 'E_S3');
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getS3JsonWithHeaders,
|
|
getS3TextWithHeaders,
|
|
};
|
|
|