Files
kvarta_project/apps/api/src/config.ts
T
nunuchek 667c6a6e6d feat(api): Telegram — темы, очередь повторов, «Система», /setup
telegram/client: Bot API на fetch с таймаутом 5 с; ошибки делятся на
повторяемые (сеть, таймаут, 429, 5xx) и конфигурационные (400/403…),
токен не попадает ни в тексты ошибок, ни в tg_last_error.
format: заявка и уточнение по шаблону плана, HTML с экранированием,
источник из utm / referrer, первый визит.
outbox: Notifier с отправкой после ответа и тиком раз в минуту, пауза
1, 2, 4… мин, уточнение только после своей заявки, после 10 попыток —
в «Систему». system: тема «Система» с троттлингом ×N за период.
bot: long polling, только TELEGRAM_ADMIN_ID, /setup создаёт темы и
присылает id в личку. config: всё окружение в одном месте.
2026-10-17 16:07:26 +03:00

60 lines
2.1 KiB
TypeScript

// Всё окружение api читается здесь. Telegram-переменные необязательны: без них api
// принимает заявки, а отправки копятся в pending до настройки.
import { DEFAULT_DB_PATH } from './db/index.js';
export type TelegramConfig = {
token?: string;
/** id группы, у супергрупп отрицательный: -100… */
chatId?: number;
leadsThreadId?: number;
systemThreadId?: number;
adminId?: number;
};
export type Config = {
port: number;
dbPath: string;
version: string;
trustedProxies: string[];
telegram: TelegramConfig;
};
type Env = Record<string, string | undefined>;
function optional(env: Env, name: string): string | undefined {
const value = env[name]?.trim();
return value ? value : undefined;
}
function optionalInt(env: Env, name: string): number | undefined {
const raw = optional(env, name);
if (raw === undefined) return undefined;
if (!/^-?\d+$/.test(raw) || !Number.isSafeInteger(Number(raw))) {
throw new Error(`${name}: ожидается целое число`);
}
return Number(raw);
}
export function loadConfig(env: Env = process.env): Config {
const port = optionalInt(env, 'PORT') ?? 3001;
const token = optional(env, 'TELEGRAM_BOT_TOKEN');
// Значение токена в текст ошибки не попадает — только факт, что формат не тот.
if (token !== undefined && !/^\d+:[\w-]+$/.test(token)) throw new Error('TELEGRAM_BOT_TOKEN: неверный формат');
return {
port,
dbPath: optional(env, 'DB_PATH') ?? DEFAULT_DB_PATH,
version: optional(env, 'APP_VERSION') ?? 'dev',
trustedProxies: (optional(env, 'TRUSTED_PROXIES') ?? '')
.split(',')
.map((s) => s.trim())
.filter(Boolean),
telegram: {
token,
chatId: optionalInt(env, 'TELEGRAM_CHAT_ID'),
leadsThreadId: optionalInt(env, 'TELEGRAM_LEADS_THREAD_ID'),
systemThreadId: optionalInt(env, 'TELEGRAM_SYSTEM_THREAD_ID'),
adminId: optionalInt(env, 'TELEGRAM_ADMIN_ID'),
},
};
}