Files
vendoo/app/modules/settings/service.mjs
T
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

137 lines
8.4 KiB
JavaScript

import { readRuntimeConfig, setConfigValue, writeRuntimeConfig } from '../../../lib/config-store.mjs';
import { getSecret, hasSecret, setSecret } from '../../../lib/secret-store.mjs';
import { isMailerConfigured, reinitMailer } from '../../../lib/mailer.mjs';
import { PlatformError } from '../../kernel/errors.mjs';
import { readApplicationSettings, writeApplicationSettings } from './repository.mjs';
const SECRET_FIELDS = Object.freeze({
anthropic_key: 'ANTHROPIC_API_KEY', openai_key: 'OPENAI_API_KEY', openrouter_key: 'OPENROUTER_API_KEY',
etsy_key: 'ETSY_API_KEY', ebay_client_secret: 'EBAY_CLIENT_SECRET', ai_model_photo_local_token: 'LOCAL_IMAGE_TOKEN',
});
const RUNTIME_FIELDS = Object.freeze({
ebay_client_id: 'EBAY_CLIENT_ID', ebay_ru_name: 'EBAY_RU_NAME', ebay_sandbox: 'EBAY_SANDBOX',
ollama_url: 'OLLAMA_URL', public_base_url: 'PUBLIC_BASE_URL', ai_model_photo_local_url: 'LOCAL_IMAGE_URL',
ai_model_photo_local_workflow_path: 'LOCAL_IMAGE_WORKFLOW_PATH', ai_model_photo_local_install_path: 'LOCAL_IMAGE_INSTALL_PATH',
});
const DB_FIELDS = new Set([
'default_platform','default_ai','default_language','seller_notes','ai_model_photos_enabled','ai_model_photo_provider',
'ai_model_photo_default_mode','ai_model_photo_default_variants','ai_model_photo_default_preset','ai_model_photo_openrouter_free_only',
'ai_model_photo_block_underwear','ai_model_photo_block_kids','ai_model_photo_preserve_logos','ai_model_photo_openrouter_model',
'ai_model_photo_openai_model','ai_model_photo_local_enabled','ai_model_photo_local_require_token','ai_model_photo_use_source_reference',
'ai_model_photo_reference_strength','ai_model_photo_local_resolution','flux_runtime_profile','flux_default_profile',
'flux_prompt_assistant','flux_default_style','flux_default_format','flux_default_variants','flux_seed_lock','flux_max_parallel_jobs',
'smtp_host','smtp_port','smtp_user','smtp_from',
]);
const ALL_FIELDS = new Set([...Object.keys(SECRET_FIELDS), ...Object.keys(RUNTIME_FIELDS), ...DB_FIELDS]);
function fail(message, status = 400, code = 'SETTINGS_INVALID') {
throw new PlatformError(message, { status, code, expose: true });
}
function cleanString(value, { max = 4096, multiline = false } = {}) {
const result = String(value ?? '').trim();
if (result.length > max) fail('Einstellungswert ist zu lang.');
if (!multiline && /[\r\n\0]/.test(result)) fail('Einstellungswert enthält unzulässige Steuerzeichen.');
return result;
}
export function createSettingsService({ etsyApi, ebayApi }) {
function decorate(settings) {
const result = { ...settings };
result.has_anthropic_key = hasSecret('ANTHROPIC_API_KEY');
result.has_openai_key = hasSecret('OPENAI_API_KEY');
result.has_openrouter_key = hasSecret('OPENROUTER_API_KEY');
result.has_etsy_key = hasSecret('ETSY_API_KEY');
result.etsy_connected = Boolean(etsyApi?.isConnected?.());
result.has_ebay_credentials = Boolean(ebayApi?.hasCredentials?.());
result.ebay_connected = Boolean(ebayApi?.isConnected?.());
result.ebay_sandbox = process.env.EBAY_SANDBOX === 'true';
result.ollama_url = process.env.OLLAMA_URL || 'http://localhost:11434';
result.public_base_url = process.env.PUBLIC_BASE_URL || '';
result.ai_model_photo_local_url = process.env.LOCAL_IMAGE_URL || result.ai_model_photo_local_url || 'http://127.0.0.1:8188';
result.ai_model_photo_local_workflow_path = process.env.LOCAL_IMAGE_WORKFLOW_PATH || result.ai_model_photo_local_workflow_path || 'local-ai/workflows/vendoo_flux_schnell_api.json';
result.ai_model_photo_local_install_path = process.env.LOCAL_IMAGE_INSTALL_PATH || result.ai_model_photo_local_install_path || 'local-ai/comfyui';
result.ai_model_photo_local_enabled = result.ai_model_photo_local_enabled || '1';
result.ai_model_photo_local_require_token = result.ai_model_photo_local_require_token || '1';
result.ai_model_photo_use_source_reference = result.ai_model_photo_use_source_reference || '1';
result.ai_model_photo_reference_strength = result.ai_model_photo_reference_strength || '70';
result.ai_model_photo_local_resolution = result.ai_model_photo_local_resolution || '768';
result.ai_model_photo_preserve_logos = result.ai_model_photo_preserve_logos || '1';
result.flux_runtime_profile = result.flux_runtime_profile || 'auto';
result.flux_default_profile = result.flux_default_profile || 'balanced';
result.flux_prompt_assistant = result.flux_prompt_assistant || '1';
result.flux_default_style = result.flux_default_style || 'photorealistic';
result.flux_default_format = result.flux_default_format || '768x768';
result.flux_default_variants = result.flux_default_variants || '1';
result.flux_seed_lock = result.flux_seed_lock || '0';
result.flux_max_parallel_jobs = result.flux_max_parallel_jobs || '1';
result.local_image_token_set = hasSecret('LOCAL_IMAGE_TOKEN');
return result;
}
function validatePatch(input) {
const source = input && typeof input === 'object' && !Array.isArray(input) ? input : {};
for (const key of Object.keys(source)) if (!ALL_FIELDS.has(key)) fail(`Unbekanntes Einstellungsfeld: ${key}`);
return source;
}
function smtpStatus() {
const settings = readApplicationSettings();
return {
smtp_host: settings.smtp_host || process.env.SMTP_HOST || '', smtp_port: settings.smtp_port || process.env.SMTP_PORT || '587',
smtp_user: settings.smtp_user || process.env.SMTP_USER || '', smtp_pass: hasSecret('SMTP_PASS') ? '••••••••' : '',
smtp_from: settings.smtp_from || process.env.SMTP_FROM || '', configured: isMailerConfigured(),
source: hasSecret('SMTP_PASS') ? 'encrypted-secret-store' : (process.env.SMTP_HOST ? 'environment' : 'none'),
};
}
return Object.freeze({
get() { return decorate(readApplicationSettings()); },
update(input) {
const source = validatePatch(input);
let envContent = readRuntimeConfig();
const dbPatch = {};
for (const [field, secretName] of Object.entries(SECRET_FIELDS)) {
if (source[field] !== undefined && source[field] !== '') setSecret(secretName, cleanString(source[field], { max: 16384 }), { source: 'settings-ui' });
}
for (const [field, envName] of Object.entries(RUNTIME_FIELDS)) {
if (source[field] === undefined) continue;
let value = cleanString(source[field], { max: 4096 });
if (['public_base_url','ai_model_photo_local_url'].includes(field)) value = value.replace(/\/$/, '');
envContent = setConfigValue(envContent, envName, value);
process.env[envName] = value;
}
for (const field of DB_FIELDS) {
if (source[field] === undefined) continue;
dbPatch[field] = cleanString(source[field], { max: field === 'seller_notes' ? 16000 : 4096, multiline: field === 'seller_notes' });
}
if (dbPatch.ai_model_photo_local_enabled !== undefined) {
const value = String(dbPatch.ai_model_photo_local_enabled) === '0' ? 'false' : 'true';
envContent = setConfigValue(envContent, 'LOCAL_IMAGE_ENABLED', value); process.env.LOCAL_IMAGE_ENABLED = value;
}
if (dbPatch.ai_model_photo_local_require_token !== undefined) {
const value = String(dbPatch.ai_model_photo_local_require_token) === '0' ? 'false' : 'true';
envContent = setConfigValue(envContent, 'LOCAL_IMAGE_REQUIRE_TOKEN', value); process.env.LOCAL_IMAGE_REQUIRE_TOKEN = value;
}
writeRuntimeConfig(envContent);
return decorate(writeApplicationSettings(dbPatch));
},
smtpStatus,
updateSmtp(input) {
const patch = {};
for (const field of ['smtp_host','smtp_port','smtp_user','smtp_from']) if (input[field] !== undefined) patch[field] = cleanString(input[field], { max: 512 });
if (input.smtp_pass !== undefined && input.smtp_pass !== '••••••••' && input.smtp_pass !== '') setSecret('SMTP_PASS', cleanString(input.smtp_pass, { max: 4096 }), { source: 'smtp-ui' });
patch.smtp_pass = '';
const settings = writeApplicationSettings(patch);
const smtpSecret = getSecret('SMTP_PASS');
if (settings.smtp_host && settings.smtp_user && smtpSecret) {
reinitMailer({ host: settings.smtp_host, port: settings.smtp_port || '587', user: settings.smtp_user, pass: smtpSecret });
if (settings.smtp_from) process.env.SMTP_FROM = settings.smtp_from;
}
return smtpStatus();
},
health() { return { ok: true, settings: Object.keys(readApplicationSettings()).length, smtp_configured: isMailerConfigured() }; },
});
}