/** * Общие роуты для JSON данных (servers, filters, billing и т.д.) */ const { writeS3JsonObject, headMeta, readS3TextObject } = require('../services/s3Service'); const { sendError, sendOk, checkIfNoneMatch } = require('../middleware/errorHandler'); /** * Создать GET эндпоинт для JSON данных * @param {string} s3Key - Ключ объекта (логический, как раньше в S3) * @param {Object} options - Опции * @param {boolean} options.singleObject - Если true, возвращает объект вместо массива * @param {any} options.defaultValue - Значение по умолчанию ([] для массива, {} для объекта) */ function createJsonDataGET(s3Key, options = {}) { const { singleObject = false, defaultValue } = options; const fallback = defaultValue !== undefined ? defaultValue : (singleObject ? {} : []); return async (req, res) => { try { const head = await headMeta(s3Key); 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; let fileContent; try { const data = await readS3TextObject(s3Key); fileContent = data.body; } catch (e) { if (e?.code === 'NoSuchKey') { return res.json(fallback); } throw e; } let items = fallback; try { const parsed = JSON.parse(fileContent); if (singleObject) { items = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? parsed : fallback; } else { items = Array.isArray(parsed) ? parsed : fallback; } } catch (parseError) { console.error(`Error parsing ${s3Key}:`, parseError); items = fallback; } res.json(items); } catch (error) { console.error(error); return sendError(res, 500, 'Error reading storage', 'E_STORAGE'); } }; } /** * Создать POST эндпоинт для JSON данных */ function createJsonDataPOST(s3Key, validateItem = null, options = {}) { const { singleObject = false } = options; return async (req, res) => { const { domains: items } = req.body; if (singleObject) { if (typeof items !== 'object' || items === null || Array.isArray(items)) { return sendError(res, 400, 'Data must be an object', 'E_BAD_REQUEST'); } if (validateItem) { const error = validateItem(items); if (error) { return sendError(res, 400, error, 'E_SCHEMA'); } } } else { if (!Array.isArray(items)) { return sendError(res, 400, 'Data must be an array', 'E_BAD_REQUEST'); } if (validateItem) { for (let i = 0; i < items.length; i++) { const error = validateItem(items[i], i); if (error) { return sendError(res, 400, error, 'E_SCHEMA'); } } } } try { const meta = await writeS3JsonObject(s3Key, items); return sendOk(res, meta); } catch (error) { console.error(error); return sendError(res, 500, 'Error writing storage', 'E_STORAGE'); } }; } function createJsonDataRoutes(s3Key, validateItem = null, options = {}) { return { get: createJsonDataGET(s3Key, options), post: createJsonDataPOST(s3Key, validateItem, options), }; } module.exports = { createJsonDataRoutes, createJsonDataGET, createJsonDataPOST, };