Normalize line endings to LF via .gitattributes
Репозиторий хранил фронтенд в CRLF, а часть бэкенда — вперемешку, хотя CI и Docker-сборка работают под Linux. Прибиваем LF атрибутом `* text=auto eol=lf` и разово нормализуем дерево, чтобы форматтеры не переписывали файлы целиком на каждом прогоне. Коммит чисто механический: изменений содержимого нет, только концы строк. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9d1c6d2fc3
commit
0442056367
@@ -1,168 +1,168 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using TeleWave.Application.Broadcast.Bumpers;
|
||||
using TeleWave.Application.Broadcast.Scheduling;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Streaming;
|
||||
using TeleWave.Domain.Broadcast.Scheduling;
|
||||
using TeleWave.Infrastructure.Broadcast;
|
||||
using TeleWave.Infrastructure.Identity;
|
||||
using TeleWave.Infrastructure.Library;
|
||||
using TeleWave.Infrastructure.Media;
|
||||
using TeleWave.Infrastructure.Metadata;
|
||||
using TeleWave.Infrastructure.Persistence;
|
||||
using TeleWave.Infrastructure.Settings;
|
||||
using TeleWave.Infrastructure.Streaming;
|
||||
|
||||
namespace TeleWave.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Регистрация сервисов инфраструктуры: EF Core (PostgreSQL), Identity/JWT-аутентификация.
|
||||
/// </summary>
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddInfrastructure(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration
|
||||
)
|
||||
{
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
options.UseNpgsql(
|
||||
configuration["ConnectionStrings:Default"]
|
||||
?? throw new InvalidOperationException(
|
||||
"Строка подключения 'ConnectionStrings:Default' не сконфигурирована."
|
||||
)
|
||||
)
|
||||
);
|
||||
services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
|
||||
|
||||
services
|
||||
.AddIdentityCore<AppUser>(options =>
|
||||
{
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.Password.RequiredLength = 8;
|
||||
options.Password.RequireDigit = true;
|
||||
options.Password.RequireUppercase = true;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
|
||||
options.Lockout.AllowedForNewUsers = true;
|
||||
})
|
||||
.AddRoles<AppRole>()
|
||||
.AddEntityFrameworkStores<AppDbContext>()
|
||||
.AddSignInManager()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName));
|
||||
services.Configure<AdminSeedOptions>(
|
||||
configuration.GetSection(AdminSeedOptions.SectionName)
|
||||
);
|
||||
|
||||
var jwtOptions =
|
||||
configuration.GetSection(JwtOptions.SectionName).Get<JwtOptions>()
|
||||
?? throw new InvalidOperationException("Секция конфигурации 'Jwt' не задана.");
|
||||
|
||||
// Fail-fast на подписывающем ключе: этот же ключ подписывает и JWT, и stream-токены
|
||||
// (StreamTokenService), поэтому placeholder/короткий ключ из appsettings.json = полный обход
|
||||
// авторизации (можно сфорджить admin-JWT). Лучше не стартовать вовсе, чем стартовать уязвимым.
|
||||
// HMAC-SHA256 требует ключ не короче размера хеша (32 байта), иначе он ослаблен нулевым паддингом.
|
||||
if (Encoding.UTF8.GetByteCount(jwtOptions.SigningKey) < 32)
|
||||
throw new InvalidOperationException(
|
||||
"Jwt:SigningKey должен быть не короче 32 байт. Задайте криптостойкий секрет через конфигурацию/переменную окружения Jwt__SigningKey."
|
||||
);
|
||||
if (jwtOptions.SigningKey.Contains("change-me", StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException(
|
||||
"Jwt:SigningKey использует значение-заглушку из appsettings.json. Переопределите его криптостойким секретом (Jwt__SigningKey)."
|
||||
);
|
||||
|
||||
services
|
||||
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = jwtOptions.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = jwtOptions.Audience,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(jwtOptions.SigningKey)
|
||||
),
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.FromSeconds(30),
|
||||
};
|
||||
});
|
||||
|
||||
services.AddAuthorization();
|
||||
|
||||
services.AddScoped<IIdentityService, IdentityService>();
|
||||
services.AddScoped<IJwtTokenService, JwtTokenService>();
|
||||
services.AddScoped<IRefreshTokenService, RefreshTokenService>();
|
||||
services.AddScoped<IRoleService, RoleService>();
|
||||
services.AddScoped<ICurrentUser, CurrentUser>();
|
||||
services.AddScoped<ISiteSettings, SiteSettings>();
|
||||
services.AddScoped<DbInitializer>();
|
||||
services.AddScoped<GenreSeeder>();
|
||||
|
||||
AddMedia(services, configuration);
|
||||
AddBroadcast(services, configuration);
|
||||
AddMetadata(services, configuration);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>Метаданные шоу/серий: провайдеры TMDb/OMDb, резолвер, локальное хранилище картинок.</summary>
|
||||
private static void AddMetadata(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<MetadataOptions>(configuration.GetSection(MetadataOptions.SectionName));
|
||||
services.AddHttpClient("metadata", client => client.Timeout = TimeSpan.FromSeconds(15));
|
||||
services.AddSingleton<IMetadataProvider, TmdbMetadataProvider>();
|
||||
services.AddSingleton<IMetadataProvider, OmdbMetadataProvider>();
|
||||
services.AddSingleton<IMetadataProviderResolver, MetadataProviderResolver>();
|
||||
}
|
||||
|
||||
/// <summary>Планировщик расписания: генератор, источник случайности и фоновый сервис горизонта.</summary>
|
||||
private static void AddBroadcast(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<SchedulerOptions>(
|
||||
configuration.GetSection(SchedulerOptions.SectionName)
|
||||
);
|
||||
services.Configure<StreamingOptions>(
|
||||
configuration.GetSection(StreamingOptions.SectionName)
|
||||
);
|
||||
services.Configure<BumperOptions>(configuration.GetSection(BumperOptions.SectionName));
|
||||
|
||||
services.AddSingleton<IRandomSource, SystemRandomSource>();
|
||||
services.AddSingleton<StreamTokenService>();
|
||||
services.AddSingleton<IBumperRenderer, FfmpegBumperRenderer>();
|
||||
services.AddHostedService<SchedulingBackgroundService>();
|
||||
services.AddHostedService<MaintenanceBackgroundService>();
|
||||
}
|
||||
|
||||
/// <summary>Хранилище медиа, обработка ffmpeg, очередь и фоновые сервисы (очередь + inbox-сканер).</summary>
|
||||
private static void AddMedia(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<StorageOptions>(configuration.GetSection(StorageOptions.SectionName));
|
||||
services.Configure<MediaOptions>(configuration.GetSection(MediaOptions.SectionName));
|
||||
|
||||
services.AddSingleton<MediaPathResolver>();
|
||||
services.AddSingleton<IMediaStorage, FileSystemMediaStorage>();
|
||||
services.AddSingleton<IBumperTemplateStorage, BumperTemplateStorage>();
|
||||
services.AddSingleton<IImageStore, ImageStore>();
|
||||
services.AddSingleton<IImageDownloader, ImageDownloader>();
|
||||
services.AddSingleton<IAudioProbe, FfprobeAudioProbe>();
|
||||
services.AddSingleton<IMediaProcessor, FfmpegMediaProcessor>();
|
||||
services.AddSingleton<IMediaProcessingQueue, MediaProcessingQueue>();
|
||||
services.AddSingleton<IBumperRenderQueue, BumperRenderQueue>();
|
||||
|
||||
services.AddHostedService<MediaProcessingBackgroundService>();
|
||||
services.AddHostedService<InboxScannerBackgroundService>();
|
||||
services.AddHostedService<BumperRenderBackgroundService>();
|
||||
}
|
||||
}
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using TeleWave.Application.Broadcast.Bumpers;
|
||||
using TeleWave.Application.Broadcast.Scheduling;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Streaming;
|
||||
using TeleWave.Domain.Broadcast.Scheduling;
|
||||
using TeleWave.Infrastructure.Broadcast;
|
||||
using TeleWave.Infrastructure.Identity;
|
||||
using TeleWave.Infrastructure.Library;
|
||||
using TeleWave.Infrastructure.Media;
|
||||
using TeleWave.Infrastructure.Metadata;
|
||||
using TeleWave.Infrastructure.Persistence;
|
||||
using TeleWave.Infrastructure.Settings;
|
||||
using TeleWave.Infrastructure.Streaming;
|
||||
|
||||
namespace TeleWave.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Регистрация сервисов инфраструктуры: EF Core (PostgreSQL), Identity/JWT-аутентификация.
|
||||
/// </summary>
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddInfrastructure(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration
|
||||
)
|
||||
{
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
options.UseNpgsql(
|
||||
configuration["ConnectionStrings:Default"]
|
||||
?? throw new InvalidOperationException(
|
||||
"Строка подключения 'ConnectionStrings:Default' не сконфигурирована."
|
||||
)
|
||||
)
|
||||
);
|
||||
services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
|
||||
|
||||
services
|
||||
.AddIdentityCore<AppUser>(options =>
|
||||
{
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.Password.RequiredLength = 8;
|
||||
options.Password.RequireDigit = true;
|
||||
options.Password.RequireUppercase = true;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Lockout.MaxFailedAccessAttempts = 5;
|
||||
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
|
||||
options.Lockout.AllowedForNewUsers = true;
|
||||
})
|
||||
.AddRoles<AppRole>()
|
||||
.AddEntityFrameworkStores<AppDbContext>()
|
||||
.AddSignInManager()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName));
|
||||
services.Configure<AdminSeedOptions>(
|
||||
configuration.GetSection(AdminSeedOptions.SectionName)
|
||||
);
|
||||
|
||||
var jwtOptions =
|
||||
configuration.GetSection(JwtOptions.SectionName).Get<JwtOptions>()
|
||||
?? throw new InvalidOperationException("Секция конфигурации 'Jwt' не задана.");
|
||||
|
||||
// Fail-fast на подписывающем ключе: этот же ключ подписывает и JWT, и stream-токены
|
||||
// (StreamTokenService), поэтому placeholder/короткий ключ из appsettings.json = полный обход
|
||||
// авторизации (можно сфорджить admin-JWT). Лучше не стартовать вовсе, чем стартовать уязвимым.
|
||||
// HMAC-SHA256 требует ключ не короче размера хеша (32 байта), иначе он ослаблен нулевым паддингом.
|
||||
if (Encoding.UTF8.GetByteCount(jwtOptions.SigningKey) < 32)
|
||||
throw new InvalidOperationException(
|
||||
"Jwt:SigningKey должен быть не короче 32 байт. Задайте криптостойкий секрет через конфигурацию/переменную окружения Jwt__SigningKey."
|
||||
);
|
||||
if (jwtOptions.SigningKey.Contains("change-me", StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException(
|
||||
"Jwt:SigningKey использует значение-заглушку из appsettings.json. Переопределите его криптостойким секретом (Jwt__SigningKey)."
|
||||
);
|
||||
|
||||
services
|
||||
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = jwtOptions.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = jwtOptions.Audience,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(jwtOptions.SigningKey)
|
||||
),
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.FromSeconds(30),
|
||||
};
|
||||
});
|
||||
|
||||
services.AddAuthorization();
|
||||
|
||||
services.AddScoped<IIdentityService, IdentityService>();
|
||||
services.AddScoped<IJwtTokenService, JwtTokenService>();
|
||||
services.AddScoped<IRefreshTokenService, RefreshTokenService>();
|
||||
services.AddScoped<IRoleService, RoleService>();
|
||||
services.AddScoped<ICurrentUser, CurrentUser>();
|
||||
services.AddScoped<ISiteSettings, SiteSettings>();
|
||||
services.AddScoped<DbInitializer>();
|
||||
services.AddScoped<GenreSeeder>();
|
||||
|
||||
AddMedia(services, configuration);
|
||||
AddBroadcast(services, configuration);
|
||||
AddMetadata(services, configuration);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>Метаданные шоу/серий: провайдеры TMDb/OMDb, резолвер, локальное хранилище картинок.</summary>
|
||||
private static void AddMetadata(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<MetadataOptions>(configuration.GetSection(MetadataOptions.SectionName));
|
||||
services.AddHttpClient("metadata", client => client.Timeout = TimeSpan.FromSeconds(15));
|
||||
services.AddSingleton<IMetadataProvider, TmdbMetadataProvider>();
|
||||
services.AddSingleton<IMetadataProvider, OmdbMetadataProvider>();
|
||||
services.AddSingleton<IMetadataProviderResolver, MetadataProviderResolver>();
|
||||
}
|
||||
|
||||
/// <summary>Планировщик расписания: генератор, источник случайности и фоновый сервис горизонта.</summary>
|
||||
private static void AddBroadcast(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<SchedulerOptions>(
|
||||
configuration.GetSection(SchedulerOptions.SectionName)
|
||||
);
|
||||
services.Configure<StreamingOptions>(
|
||||
configuration.GetSection(StreamingOptions.SectionName)
|
||||
);
|
||||
services.Configure<BumperOptions>(configuration.GetSection(BumperOptions.SectionName));
|
||||
|
||||
services.AddSingleton<IRandomSource, SystemRandomSource>();
|
||||
services.AddSingleton<StreamTokenService>();
|
||||
services.AddSingleton<IBumperRenderer, FfmpegBumperRenderer>();
|
||||
services.AddHostedService<SchedulingBackgroundService>();
|
||||
services.AddHostedService<MaintenanceBackgroundService>();
|
||||
}
|
||||
|
||||
/// <summary>Хранилище медиа, обработка ffmpeg, очередь и фоновые сервисы (очередь + inbox-сканер).</summary>
|
||||
private static void AddMedia(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<StorageOptions>(configuration.GetSection(StorageOptions.SectionName));
|
||||
services.Configure<MediaOptions>(configuration.GetSection(MediaOptions.SectionName));
|
||||
|
||||
services.AddSingleton<MediaPathResolver>();
|
||||
services.AddSingleton<IMediaStorage, FileSystemMediaStorage>();
|
||||
services.AddSingleton<IBumperTemplateStorage, BumperTemplateStorage>();
|
||||
services.AddSingleton<IImageStore, ImageStore>();
|
||||
services.AddSingleton<IImageDownloader, ImageDownloader>();
|
||||
services.AddSingleton<IAudioProbe, FfprobeAudioProbe>();
|
||||
services.AddSingleton<IMediaProcessor, FfmpegMediaProcessor>();
|
||||
services.AddSingleton<IMediaProcessingQueue, MediaProcessingQueue>();
|
||||
services.AddSingleton<IBumperRenderQueue, BumperRenderQueue>();
|
||||
|
||||
services.AddHostedService<MediaProcessingBackgroundService>();
|
||||
services.AddHostedService<InboxScannerBackgroundService>();
|
||||
services.AddHostedService<BumperRenderBackgroundService>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user