Files
Deal/src/core/Deal.Api/Hosting/OperatorBootstrapHostedService.cs
T
Rustam Khalimov 9e07568ddd Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы
ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ,
инструкция пользователя, техдокументация, код-стайл), бэклог,
скрипты развёртывания и архив прототипа LeadRadar.
2026-09-11 02:50:17 +03:00

97 lines
5.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Deal.Modules.Tenants.Application;
namespace Deal.Api.Hosting;
/// <summary>
/// Hosted-шаг bootstrap оператора при старте (Ruling 1 этапа 7): env DEAL_OPERATOR_* → OperatorBootstrapService.EnsureOperatorAsync.
/// </summary>
/// <remarks>
/// Регистрируется после <see cref="TenantBootstrapService"/> и работает тем же паттерном: OperatorBootstrapService —
/// scoped (его IOperatorAuthStore живёт на scoped DealDbContext), поэтому резолвится в собственном scope из
/// IServiceScopeFactory (как TenantService в TenantBootstrapService). Идемпотентен: существующего оператора
/// не пересоздаёт и пароль не перезаписывает. В Development без env-кред используется dev-дефолт
/// operator/operator (зеркало dev-seed admin/admin, логируется как dev-режим); в Production без env-кред
/// шаг пропускается с warning — оператора заводит админ позже через env и рестарт хоста (кода регистрации
/// оператора нет). Частичная конфигурация (задана ровно одна из DEAL_OPERATOR_LOGIN/DEAL_OPERATOR_PASSWORD)
/// логируется warning — не молчаливый дефолт (решение ревью Task 2): в Development используются dev-дефолты,
/// в Production шаг пропускается. Секреты (пароли) в логи не пишутся (правило этапов 1–6).
/// </remarks>
public sealed class OperatorBootstrapHostedService(
IServiceScopeFactory scopeFactory,
IConfiguration configuration,
IHostEnvironment environment,
ILogger<OperatorBootstrapHostedService> logger) : IHostedService
{
/// <inheritdoc />
public async Task StartAsync(CancellationToken ct)
{
await using var scope = scopeFactory.CreateAsyncScope();
var operatorBootstrapService = scope.ServiceProvider.GetRequiredService<OperatorBootstrapService>();
var login = configuration[OperatorBootstrapService.LoginEnvKey];
var password = configuration[OperatorBootstrapService.PasswordEnvKey];
bool hasLogin = !string.IsNullOrWhiteSpace(login);
bool hasPassword = !string.IsNullOrWhiteSpace(password);
bool allowDevelopmentDefaults = environment.IsDevelopment();
// Предупреждения о пропуске/частичной конфигурации логируются ДО вызова шага — сам шаг
// в этих случаях ничего не создаёт (EnsureOperatorAsync возвращает null, хранилище не тронуто).
if (!hasLogin || !hasPassword)
{
if (hasLogin != hasPassword)
{
LogPartialConfigurationWarning(hasLogin, allowDevelopmentDefaults);
}
else if (!allowDevelopmentDefaults)
{
// Production без кред: пропуск с warning (Ruling 1) — кода регистрации оператора нет.
logger.LogWarning(
"DEAL_OPERATOR_LOGIN/DEAL_OPERATOR_PASSWORD не заданы (Production) — bootstrap оператора "
+ "пропущен. Оператор заводится позже: задайте env DEAL_OPERATOR_* и перезапустите хост.");
}
else
{
// Development без кред: штатный dev-дефолт (зеркало dev-seed admin/admin).
logger.LogInformation(
"DEAL_OPERATOR_LOGIN/DEAL_OPERATOR_PASSWORD не заданы (Development) — используется "
+ "dev-дефолт оператора {DefaultLogin}.",
OperatorBootstrapService.DefaultOperatorLogin);
}
}
var ensuredLogin = await operatorBootstrapService.EnsureOperatorAsync(
login, password, allowDevelopmentDefaults, ct);
if (ensuredLogin is not null)
{
logger.LogInformation("Bootstrap оператора: оператор {Login} присутствует.", ensuredLogin);
}
}
/// <inheritdoc />
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
// Логирует warning о неполной env-конфигурации (задана одна из двух переменных).
// hasLogin: Задан ли логин (пароль при этом пуст).
// allowDevelopmentDefaults: Разрешены ли dev-дефолты (Development).
private void LogPartialConfigurationWarning(bool hasLogin, bool allowDevelopmentDefaults)
{
string missingEnv = hasLogin
? OperatorBootstrapService.PasswordEnvKey
: OperatorBootstrapService.LoginEnvKey;
if (allowDevelopmentDefaults)
{
logger.LogWarning(
"Env-конфигурация оператора неполна: не задан {MissingEnv}. В Development используются "
+ "dev-дефолты оператора {DefaultLogin}.",
missingEnv, OperatorBootstrapService.DefaultOperatorLogin);
}
else
{
logger.LogWarning(
"Env-конфигурация оператора неполна: не задан {MissingEnv}. Bootstrap оператора в Production "
+ "пропущен — оператор заводится позже через env DEAL_OPERATOR_* и рестарт хоста.",
missingEnv);
}
}
}