require('dotenv').config(); // settings подгружаем лениво, чтобы избежать циклической зависимости // (settings → config/db → config/index). require здесь синхронный, но settings.getMany async. let _settingsSvc = null; function settingsSvc() { if (!_settingsSvc) _settingsSvc = require('../services/settings'); return _settingsSvc; } const config = { port: parseInt(process.env.ZEROPOST_PORT || 3030), // AI gateway — значения мутируются reloadAi() из app_settings. // Дефолты ниже — последний fallback, если БД и .env пустые. ai: { baseUrl: process.env.AI_BASE_URL || 'https://aiprimetech.io/v1', apiKey: process.env.AI_API_KEY || null, imageApiKey: process.env.AI_IMAGE_API_KEY || process.env.AI_API_KEY || null, imageBaseUrl: process.env.AI_IMAGE_BASE_URL || 'https://api.aiguoguo199.com/v1', imageModel: process.env.AI_MODEL_IMAGE || 'gpt-image-1-mini', imageModelViaResponses: process.env.AI_MODEL_IMAGE_VIA_RESPONSES || 'gpt-5.5', models: { post: process.env.AI_MODEL_POST || 'claude-haiku-4-5-20251001', article: process.env.AI_MODEL_ARTICLE || 'claude-sonnet-4-6', topics: process.env.AI_MODEL_TOPICS || 'claude-haiku-4-5-20251001', image: process.env.AI_MODEL_IMAGE || 'gpt-image-1-mini', }, }, db: { host: process.env.DB_HOST || 'localhost', port: process.env.DB_PORT || 5432, database: process.env.DB_NAME || 'zeropost', user: process.env.DB_USER || 'postgres', password: process.env.DB_PASS || 'postgres', }, redis: { host: process.env.REDIS_HOST || 'localhost', port: process.env.REDIS_PORT || 6379, }, internalSecret: process.env.INTERNAL_SECRET || 'dev-secret-change-in-prod', }; /** * Перегружает блок ai из app_settings. * Приоритет: app_settings.value → process.env[НОВОЕ_ИМЯ] → process.env[СТАРОЕ_ИМЯ] → дефолт. * Старое имя ENV нужно для безопасной миграции — если БД пуста, .env держит прод на ногах. * Мутирует config.ai в месте, чтобы все require'нувшие config продолжали видеть актуальные значения. */ async function reloadAi() { const keys = [ 'AI_TEXT_BASE_URL', 'AI_TEXT_API_KEY', 'AI_TEXT_MODEL_POST', 'AI_TEXT_MODEL_ARTICLE', 'AI_TEXT_MODEL_TOPICS', 'ROUTERAI_BASE_URL', 'ROUTERAI_API_KEY', ]; let s = {}; try { s = await settingsSvc().getMany(keys); } catch (err) { console.warn('[config.reloadAi] DB unavailable, using ENV fallback:', err.message); } const pick = (dbKey, envOld, def) => (s[dbKey] && s[dbKey].trim()) || process.env[dbKey] || process.env[envOld] || def; // Текст — aiprimetech.io config.ai.baseUrl = pick('AI_TEXT_BASE_URL', 'AI_BASE_URL', 'https://aiprimetech.io/v1'); config.ai.apiKey = pick('AI_TEXT_API_KEY', 'AI_API_KEY', null); config.ai.models.post = pick('AI_TEXT_MODEL_POST', 'AI_MODEL_POST', 'claude-haiku-4-5-20251001'); config.ai.models.article = pick('AI_TEXT_MODEL_ARTICLE', 'AI_MODEL_ARTICLE', 'claude-sonnet-4-6'); config.ai.models.topics = pick('AI_TEXT_MODEL_TOPICS', 'AI_MODEL_TOPICS', 'claude-haiku-4-5-20251001'); // Картинки — routerai.ru (только gpt-5-image-mini, /responses endpoint) // Реальная цена: ₽2.72/картинка (4175 image tokens × ₽0.000747 + input - output discount) config.ai.routeraiBaseUrl = (s['ROUTERAI_BASE_URL'] || 'https://routerai.ru/api/v1').trim(); config.ai.routeraiApiKey = (s['ROUTERAI_API_KEY'] || '').trim() || null; config.ai.routeraiModel = 'openai/gpt-5-image-mini'; // единственная модель, quality всегда high return config.ai; } config.reloadAi = reloadAi; module.exports = config;