From a0533a8ed8be1fe23af9a3149a0b00fc4abdf607 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Sun, 26 Jul 2026 02:58:17 +0300 Subject: [PATCH] Refactor user and media endpoints: remove unused GetUser and GetMedia methods, streamline AdminUserEndpoints and MediaEndpoints, and enhance metadata handling in MetadataEndpoints. Update Program.cs to remove legacy image relocation logic and improve overall code clarity. --- .../Endpoints/AdminUserEndpoints.cs | 12 - .../TeleWave.Api/Endpoints/MediaEndpoints.cs | 12 - .../Endpoints/MetadataEndpoints.cs | 62 ----- .../Endpoints/SettingsEndpoints.cs | 10 +- backend/src/TeleWave.Api/Program.cs | 1 - .../Admin/Users/GetUser/GetUserQuery.cs | 7 - .../Users/GetUser/GetUserQueryHandler.cs | 20 -- .../Scheduling/ScheduleBumperResolver.cs | 2 +- .../Common/Interfaces/ICurrentUser.cs | 2 - .../Common/Interfaces/IIdentityService.cs | 10 +- .../Common/Interfaces/IMediaStorage.cs | 6 +- .../Media/GetMedia/GetMediaAssetQuery.cs | 6 - .../GetMedia/GetMediaAssetQueryHandler.cs | 24 -- .../Media/Stats/GetMediaStatsQueryHandler.cs | 9 +- .../FindMissingEpisodesQueryHandler.cs | 12 +- .../Metadata/MetadataErrors.cs | 5 - .../UpdateSiteSettingsCommand.cs | 6 +- .../Identity/CurrentUser.cs | 4 - .../Identity/IdentityService.cs | 23 +- .../Media/BumperRenderBackgroundService.cs | 12 +- .../Media/BumperRenderQueue.cs | 18 +- .../Media/FfmpegBumperRenderer.cs | 15 +- .../Media/FfmpegMediaProcessor.cs | 30 +-- .../Media/FfmpegText.cs | 18 ++ .../Media/MediaPathResolver.cs | 42 ---- .../Media/MediaProcessingQueue.cs | 19 +- .../Media/ProcessRunner.cs | 15 +- .../Media/SignalQueue.cs | 26 +++ .../Metadata/MetadataJson.cs | 53 +++++ .../Metadata/OmdbMetadataProvider.cs | 48 +--- .../Metadata/TmdbMetadataProvider.cs | 49 +--- ...20260725202904_BumperAssetRenderContext.cs | 21 +- .../20260725210453_MediaProcessingTiming.cs | 14 +- .../Migrations/20260725225737_ShowAudience.cs | 7 +- .../Persistence/AppDbContext.cs | 5 +- .../Persistence/MigrationExtensions.cs | 96 -------- .../Settings/SiteSettings.cs | 12 +- .../Auth/LoginCommandHandlerTests.cs | 8 +- .../Auth/RegisterCommandHandlerTests.cs | 4 +- .../Broadcast/QueryHandlersTests.cs | 26 --- .../Metadata/FindMissingEpisodesTests.cs | 8 +- .../PostgresFixture.cs | 3 +- .../TransactionIntegrationTests.cs | 10 +- frontend/package.json | 4 +- frontend/pnpm-lock.yaml | 220 ++---------------- frontend/src/features/auth/api.ts | 6 +- frontend/src/shared/api/types.ts | 5 - frontend/src/shared/lib/i18n.ts | 46 ---- frontend/src/shared/ui/card.tsx | 5 - frontend/src/shared/ui/dialog.tsx | 1 - 50 files changed, 230 insertions(+), 849 deletions(-) delete mode 100644 backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQuery.cs delete mode 100644 backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQueryHandler.cs delete mode 100644 backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQuery.cs delete mode 100644 backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQueryHandler.cs create mode 100644 backend/src/TeleWave.Infrastructure/Media/FfmpegText.cs create mode 100644 backend/src/TeleWave.Infrastructure/Media/SignalQueue.cs create mode 100644 backend/src/TeleWave.Infrastructure/Metadata/MetadataJson.cs diff --git a/backend/src/TeleWave.Api/Endpoints/AdminUserEndpoints.cs b/backend/src/TeleWave.Api/Endpoints/AdminUserEndpoints.cs index d2eb9a1..b54049c 100644 --- a/backend/src/TeleWave.Api/Endpoints/AdminUserEndpoints.cs +++ b/backend/src/TeleWave.Api/Endpoints/AdminUserEndpoints.cs @@ -3,7 +3,6 @@ using TeleWave.Api.Common; using TeleWave.Application.Admin.Users.BlockUser; using TeleWave.Application.Admin.Users.CreateUser; using TeleWave.Application.Admin.Users.DeleteUser; -using TeleWave.Application.Admin.Users.GetUser; using TeleWave.Application.Admin.Users.ListUsers; using TeleWave.Application.Admin.Users.ResetPassword; using TeleWave.Application.Admin.Users.UnblockUser; @@ -23,7 +22,6 @@ public static class AdminUserEndpoints admin.MapGet("", ListUsers).Produces>(); admin.MapPost("", CreateUser).Produces(StatusCodes.Status201Created); - admin.MapGet("/{id:guid}", GetUser).Produces(); admin.MapPost("/{id:guid}/block", BlockUser).Produces(StatusCodes.Status204NoContent); admin.MapPost("/{id:guid}/unblock", UnblockUser).Produces(StatusCodes.Status204NoContent); admin @@ -79,16 +77,6 @@ public static class AdminUserEndpoints : result.ToHttpResult(); } - private static async Task GetUser( - Guid id, - ISender sender, - CancellationToken cancellationToken - ) - { - var result = await sender.Send(new GetUserQuery(id), cancellationToken); - return result.ToHttpResult(); - } - private static async Task BlockUser( Guid id, ISender sender, diff --git a/backend/src/TeleWave.Api/Endpoints/MediaEndpoints.cs b/backend/src/TeleWave.Api/Endpoints/MediaEndpoints.cs index ae8cf4f..2277204 100644 --- a/backend/src/TeleWave.Api/Endpoints/MediaEndpoints.cs +++ b/backend/src/TeleWave.Api/Endpoints/MediaEndpoints.cs @@ -5,7 +5,6 @@ using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Common.Models; using TeleWave.Application.Media; using TeleWave.Application.Media.Delete; -using TeleWave.Application.Media.GetMedia; using TeleWave.Application.Media.ListMedia; using TeleWave.Application.Media.Register; using TeleWave.Application.Media.Stats; @@ -26,7 +25,6 @@ public static class MediaEndpoints admin.MapPost("", Upload).Produces(StatusCodes.Status201Created); admin.MapGet("", List).Produces>(); admin.MapGet("/stats", Stats).Produces(); - admin.MapGet("/{id:guid}", Get).Produces(); admin.MapDelete("/{id:guid}", Delete).Produces(StatusCodes.Status204NoContent); return app; @@ -128,16 +126,6 @@ public static class MediaEndpoints return Results.Ok(result); } - private static async Task Get( - Guid id, - ISender sender, - CancellationToken cancellationToken - ) - { - var result = await sender.Send(new GetMediaAssetQuery(id), cancellationToken); - return result.ToHttpResult(); - } - private static async Task Delete( Guid id, ISender sender, diff --git a/backend/src/TeleWave.Api/Endpoints/MetadataEndpoints.cs b/backend/src/TeleWave.Api/Endpoints/MetadataEndpoints.cs index 8e84cdc..b238a9b 100644 --- a/backend/src/TeleWave.Api/Endpoints/MetadataEndpoints.cs +++ b/backend/src/TeleWave.Api/Endpoints/MetadataEndpoints.cs @@ -1,8 +1,5 @@ using LiteCqrs; using TeleWave.Api.Common; -using TeleWave.Application.Common.Interfaces; -using TeleWave.Application.Images.DeleteImage; -using TeleWave.Application.Images.UploadImage; using TeleWave.Application.Metadata; using TeleWave.Application.Metadata.ApplyShowMetadata; using TeleWave.Application.Metadata.ClearShowMetadata; @@ -12,25 +9,12 @@ using TeleWave.Application.Metadata.RefreshEpisodes; using TeleWave.Application.Metadata.SearchShows; using TeleWave.Application.Metadata.SetShowPoster; using TeleWave.Application.Metadata.UpdateShowMetadata; -using TeleWave.Domain.Images; using TeleWave.Infrastructure.Identity; namespace TeleWave.Api.Endpoints; public static class MetadataEndpoints { - private const long MaxPosterBytes = 10L * 1024 * 1024; // 10 МБ - - private static readonly IReadOnlySet PosterExtensions = new HashSet( - StringComparer.OrdinalIgnoreCase - ) - { - ".jpg", - ".jpeg", - ".png", - ".webp", - }; - public static IEndpointRouteBuilder MapMetadataEndpoints(this IEndpointRouteBuilder app) { var admin = app.MapGroup("/api/admin/metadata") @@ -42,9 +26,6 @@ public static class MetadataEndpoints admin.MapPost("/shows/{showId:guid}/apply", Apply).Produces(StatusCodes.Status204NoContent); admin.MapPut("/shows/{showId:guid}", Update).Produces(StatusCodes.Status204NoContent); admin.MapDelete("/shows/{showId:guid}", Clear).Produces(StatusCodes.Status204NoContent); - admin - .MapPut("/shows/{showId:guid}/poster", UploadPoster) - .Produces(StatusCodes.Status204NoContent); admin .MapPut("/shows/{showId:guid}/poster-image", SetPosterImage) .Produces(StatusCodes.Status204NoContent); @@ -118,49 +99,6 @@ public static class MetadataEndpoints return result.ToHttpResult(); } - private static async Task UploadPoster( - Guid showId, - string fileName, - HttpRequest request, - IImageStore imageStore, - ISender sender, - CancellationToken cancellationToken - ) - { - var ext = Path.GetExtension(fileName).ToLowerInvariant(); - if ( - request.ContentLength is > MaxPosterBytes or 0 or null - || !PosterExtensions.Contains(ext) - ) - return MetadataErrors.InvalidPoster.ToProblem(); - - // Регистрируем постер в общем реестре (категория ShowPoster) и привязываем к шоу. - var created = await sender.Send( - new UploadImageCommand(ImageCategory.ShowPoster, ext, fileName), - cancellationToken - ); - if (!created.IsSuccess) - return created.ToHttpResult(); - - try - { - await imageStore.SaveAsync(created.Value, ext, request.Body, cancellationToken); - } - catch - { - await sender.Send(new DeleteImageCommand(created.Value), cancellationToken); - throw; - } - - var result = await sender.Send( - new SetShowPosterCommand(showId, created.Value), - cancellationToken - ); - if (!result.IsSuccess) - await sender.Send(new DeleteImageCommand(created.Value), cancellationToken); - return result.ToHttpResult(); - } - private static async Task SetPosterImage( Guid showId, SetPosterImageBody body, diff --git a/backend/src/TeleWave.Api/Endpoints/SettingsEndpoints.cs b/backend/src/TeleWave.Api/Endpoints/SettingsEndpoints.cs index 6848986..d52763f 100644 --- a/backend/src/TeleWave.Api/Endpoints/SettingsEndpoints.cs +++ b/backend/src/TeleWave.Api/Endpoints/SettingsEndpoints.cs @@ -37,11 +37,17 @@ public static class SettingsEndpoints ) { var result = await sender.Send( - new UpdateSiteSettingsCommand(body.RegistrationEnabled, body.PreferredAudioLanguages ?? ""), + new UpdateSiteSettingsCommand( + body.RegistrationEnabled, + body.PreferredAudioLanguages ?? "" + ), cancellationToken ); return result.ToHttpResult(); } } -public sealed record UpdateSiteSettingsBody(bool RegistrationEnabled, string? PreferredAudioLanguages); +public sealed record UpdateSiteSettingsBody( + bool RegistrationEnabled, + string? PreferredAudioLanguages +); diff --git a/backend/src/TeleWave.Api/Program.cs b/backend/src/TeleWave.Api/Program.cs index 7f78dac..f04628e 100644 --- a/backend/src/TeleWave.Api/Program.cs +++ b/backend/src/TeleWave.Api/Program.cs @@ -98,7 +98,6 @@ var app = builder.Build(); // Авто-применение миграций и идемпотентный сидинг (роли + админ из env) на старте. await app.Services.ApplyMigrationsAsync(); -await app.Services.RelocateLegacyImagesAsync(); await app.Services.SeedDataAsync(); app.UseForwardedHeaders(); diff --git a/backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQuery.cs b/backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQuery.cs deleted file mode 100644 index eb5bbda..0000000 --- a/backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQuery.cs +++ /dev/null @@ -1,7 +0,0 @@ -using LiteCqrs; -using TeleWave.Application.Common.Interfaces; -using TeleWave.Application.Common.Models; - -namespace TeleWave.Application.Admin.Users.GetUser; - -public sealed record GetUserQuery(Guid Id) : IQuery>; diff --git a/backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQueryHandler.cs b/backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQueryHandler.cs deleted file mode 100644 index 04b147b..0000000 --- a/backend/src/TeleWave.Application/Admin/Users/GetUser/GetUserQueryHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using LiteCqrs; -using TeleWave.Application.Common.Interfaces; -using TeleWave.Application.Common.Models; - -namespace TeleWave.Application.Admin.Users.GetUser; - -public sealed class GetUserQueryHandler(IIdentityService identityService) - : IQueryHandler> -{ - public async Task> Handle( - GetUserQuery query, - CancellationToken cancellationToken - ) - { - var user = await identityService.GetUserAsync(query.Id, cancellationToken); - return user is null - ? Result.Failure(UserErrors.NotFound) - : Result.Success(user); - } -} diff --git a/backend/src/TeleWave.Application/Broadcast/Scheduling/ScheduleBumperResolver.cs b/backend/src/TeleWave.Application/Broadcast/Scheduling/ScheduleBumperResolver.cs index 2c09321..d20c14f 100644 --- a/backend/src/TeleWave.Application/Broadcast/Scheduling/ScheduleBumperResolver.cs +++ b/backend/src/TeleWave.Application/Broadcast/Scheduling/ScheduleBumperResolver.cs @@ -1,13 +1,13 @@ using System.Security.Cryptography; using System.Text; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; using TeleWave.Application.Broadcast.Bumpers; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Streaming; using TeleWave.Domain.Broadcast; using TeleWave.Domain.Broadcast.Scheduling; using TeleWave.Domain.Media; -using Microsoft.Extensions.Options; namespace TeleWave.Application.Broadcast.Scheduling; diff --git a/backend/src/TeleWave.Application/Common/Interfaces/ICurrentUser.cs b/backend/src/TeleWave.Application/Common/Interfaces/ICurrentUser.cs index 3085a1c..7e14e8f 100644 --- a/backend/src/TeleWave.Application/Common/Interfaces/ICurrentUser.cs +++ b/backend/src/TeleWave.Application/Common/Interfaces/ICurrentUser.cs @@ -3,6 +3,4 @@ namespace TeleWave.Application.Common.Interfaces; public interface ICurrentUser { Guid? UserId { get; } - string? UserName { get; } - bool IsAuthenticated { get; } } diff --git a/backend/src/TeleWave.Application/Common/Interfaces/IIdentityService.cs b/backend/src/TeleWave.Application/Common/Interfaces/IIdentityService.cs index e0ad62e..486d5ce 100644 --- a/backend/src/TeleWave.Application/Common/Interfaces/IIdentityService.cs +++ b/backend/src/TeleWave.Application/Common/Interfaces/IIdentityService.cs @@ -2,13 +2,7 @@ using TeleWave.Application.Common.Models; namespace TeleWave.Application.Common.Interfaces; -public sealed record CurrentUserProfile( - Guid Id, - string UserName, - Guid RoleId, - string Role, - bool IsBlocked -); +public sealed record CurrentUserProfile(Guid Id, string UserName, string Role, bool IsBlocked); public sealed record UserSummaryDto( Guid Id, @@ -72,6 +66,4 @@ public interface IIdentityService bool desc, CancellationToken cancellationToken ); - - Task GetUserAsync(Guid userId, CancellationToken cancellationToken); } diff --git a/backend/src/TeleWave.Application/Common/Interfaces/IMediaStorage.cs b/backend/src/TeleWave.Application/Common/Interfaces/IMediaStorage.cs index 3714726..f8c725a 100644 --- a/backend/src/TeleWave.Application/Common/Interfaces/IMediaStorage.cs +++ b/backend/src/TeleWave.Application/Common/Interfaces/IMediaStorage.cs @@ -37,5 +37,9 @@ public interface IMediaStorage ); /// Удаляет все артефакты ассета: исходник в originals/ и каталог сегментов assets/{id}/. - Task DeleteAssetArtifactsAsync(Guid assetId, string extension, CancellationToken cancellationToken); + Task DeleteAssetArtifactsAsync( + Guid assetId, + string extension, + CancellationToken cancellationToken + ); } diff --git a/backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQuery.cs b/backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQuery.cs deleted file mode 100644 index cbf1b23..0000000 --- a/backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQuery.cs +++ /dev/null @@ -1,6 +0,0 @@ -using LiteCqrs; -using TeleWave.Application.Common.Models; - -namespace TeleWave.Application.Media.GetMedia; - -public sealed record GetMediaAssetQuery(Guid Id) : IQuery>; diff --git a/backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQueryHandler.cs b/backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQueryHandler.cs deleted file mode 100644 index 211acc7..0000000 --- a/backend/src/TeleWave.Application/Media/GetMedia/GetMediaAssetQueryHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -using LiteCqrs; -using Microsoft.EntityFrameworkCore; -using TeleWave.Application.Common.Interfaces; -using TeleWave.Application.Common.Models; - -namespace TeleWave.Application.Media.GetMedia; - -public sealed class GetMediaAssetQueryHandler(IAppDbContext dbContext) - : IQueryHandler> -{ - public async Task> Handle( - GetMediaAssetQuery query, - CancellationToken cancellationToken - ) - { - var asset = await dbContext - .MediaAssets.AsNoTracking() - .FirstOrDefaultAsync(x => x.Id == query.Id, cancellationToken); - - return asset is null - ? Result.Failure(MediaErrors.NotFound) - : Result.Success(MediaAssetDto.From(asset)); - } -} diff --git a/backend/src/TeleWave.Application/Media/Stats/GetMediaStatsQueryHandler.cs b/backend/src/TeleWave.Application/Media/Stats/GetMediaStatsQueryHandler.cs index f7436d5..ceb46e4 100644 --- a/backend/src/TeleWave.Application/Media/Stats/GetMediaStatsQueryHandler.cs +++ b/backend/src/TeleWave.Application/Media/Stats/GetMediaStatsQueryHandler.cs @@ -17,9 +17,14 @@ public sealed class GetMediaStatsQueryHandler(IAppDbContext dbContext) ) { // Сгенерированные (ТВ-заставки) в статистику библиотеки не входят. - var assets = dbContext.MediaAssets.AsNoTracking().Where(x => x.Source != MediaSource.Generated); + var assets = dbContext + .MediaAssets.AsNoTracking() + .Where(x => x.Source != MediaSource.Generated); - var queued = await assets.CountAsync(x => x.Status == MediaAssetStatus.Pending, cancellationToken); + var queued = await assets.CountAsync( + x => x.Status == MediaAssetStatus.Pending, + cancellationToken + ); var processing = await assets.CountAsync( x => x.Status == MediaAssetStatus.Processing, cancellationToken diff --git a/backend/src/TeleWave.Application/Metadata/FindMissingEpisodes/FindMissingEpisodesQueryHandler.cs b/backend/src/TeleWave.Application/Metadata/FindMissingEpisodes/FindMissingEpisodesQueryHandler.cs index 13202cd..9481cde 100644 --- a/backend/src/TeleWave.Application/Metadata/FindMissingEpisodes/FindMissingEpisodesQueryHandler.cs +++ b/backend/src/TeleWave.Application/Metadata/FindMissingEpisodes/FindMissingEpisodesQueryHandler.cs @@ -44,7 +44,10 @@ public sealed class FindMissingEpisodesQueryHandler( { var season = episode.Season; var number = episode.Episode; - if ((season is null || number is null) && names.TryGetValue(episode.MediaAssetId, out var name)) + if ( + (season is null || number is null) + && names.TryGetValue(episode.MediaAssetId, out var name) + ) { if (EpisodeName.Parse(name) is { } parsed) (season, number) = (parsed.Season, parsed.Episode); @@ -66,10 +69,9 @@ public sealed class FindMissingEpisodesQueryHandler( season, cancellationToken ); - var missing = - expected is { } exp - ? Enumerable.Range(1, exp).Where(n => !loaded.Contains(n)).ToList() - : []; + var missing = expected is { } exp + ? Enumerable.Range(1, exp).Where(n => !loaded.Contains(n)).ToList() + : []; seasons.Add(new SeasonGapDto(season, expected, loaded.Count, missing)); } diff --git a/backend/src/TeleWave.Application/Metadata/MetadataErrors.cs b/backend/src/TeleWave.Application/Metadata/MetadataErrors.cs index 35d6e95..25966f8 100644 --- a/backend/src/TeleWave.Application/Metadata/MetadataErrors.cs +++ b/backend/src/TeleWave.Application/Metadata/MetadataErrors.cs @@ -14,11 +14,6 @@ public static class MetadataErrors "Метаданные по этому источнику не найдены." ); - public static readonly Error InvalidPoster = Error.Validation( - "Metadata.InvalidPoster", - "Недопустимый файл постера (формат или размер)." - ); - public static readonly Error NoLinkedSource = Error.Validation( "Metadata.NoLinkedSource", "У шоу не привязан внешний источник — сначала найдите шоу в TMDb/OMDb." diff --git a/backend/src/TeleWave.Application/Settings/UpdateSiteSettings/UpdateSiteSettingsCommand.cs b/backend/src/TeleWave.Application/Settings/UpdateSiteSettings/UpdateSiteSettingsCommand.cs index 628e89e..e0d1afb 100644 --- a/backend/src/TeleWave.Application/Settings/UpdateSiteSettings/UpdateSiteSettingsCommand.cs +++ b/backend/src/TeleWave.Application/Settings/UpdateSiteSettings/UpdateSiteSettingsCommand.cs @@ -3,5 +3,7 @@ using TeleWave.Application.Common.Models; namespace TeleWave.Application.Settings.UpdateSiteSettings; -public sealed record UpdateSiteSettingsCommand(bool RegistrationEnabled, string PreferredAudioLanguages) - : ICommand; +public sealed record UpdateSiteSettingsCommand( + bool RegistrationEnabled, + string PreferredAudioLanguages +) : ICommand; diff --git a/backend/src/TeleWave.Infrastructure/Identity/CurrentUser.cs b/backend/src/TeleWave.Infrastructure/Identity/CurrentUser.cs index 3b6f0c9..cfebdbc 100644 --- a/backend/src/TeleWave.Infrastructure/Identity/CurrentUser.cs +++ b/backend/src/TeleWave.Infrastructure/Identity/CurrentUser.cs @@ -10,10 +10,6 @@ internal sealed class CurrentUser(IHttpContextAccessor httpContextAccessor) : IC public Guid? UserId => ParseHttpUserId(); - public string? UserName => Principal?.FindFirstValue(ClaimTypes.Name); - - public bool IsAuthenticated => Principal?.Identity?.IsAuthenticated ?? false; - private Guid? ParseHttpUserId() { var value = Principal?.FindFirstValue(ClaimTypes.NameIdentifier); diff --git a/backend/src/TeleWave.Infrastructure/Identity/IdentityService.cs b/backend/src/TeleWave.Infrastructure/Identity/IdentityService.cs index 1285ad2..48461be 100644 --- a/backend/src/TeleWave.Infrastructure/Identity/IdentityService.cs +++ b/backend/src/TeleWave.Infrastructure/Identity/IdentityService.cs @@ -11,7 +11,6 @@ namespace TeleWave.Infrastructure.Identity; internal sealed class IdentityService( UserManager userManager, SignInManager signInManager, - RoleManager roleManager, AppDbContext dbContext ) : IIdentityService { @@ -72,15 +71,8 @@ internal sealed class IdentityService( return null; var role = await GetPrimaryRoleAsync(user); - var roleEntity = await roleManager.FindByNameAsync(role); - return new CurrentUserProfile( - user.Id, - user.UserName!, - roleEntity?.Id ?? Guid.Empty, - role, - user.IsBlocked - ); + return new CurrentUserProfile(user.Id, user.UserName!, role, user.IsBlocked); } public async Task ChangePasswordAsync( @@ -260,19 +252,6 @@ internal sealed class IdentityService( return new PagedList(items, total, page, pageSize); } - public async Task GetUserAsync( - Guid userId, - CancellationToken cancellationToken - ) - { - var user = await userManager.FindByIdAsync(userId.ToString()); - if (user is null) - return null; - - var role = await GetPrimaryRoleAsync(user); - return new UserSummaryDto(user.Id, user.UserName!, role, user.IsBlocked, user.CreatedAt); - } - private async Task GetPrimaryRoleAsync(AppUser user) { var roles = await userManager.GetRolesAsync(user); diff --git a/backend/src/TeleWave.Infrastructure/Media/BumperRenderBackgroundService.cs b/backend/src/TeleWave.Infrastructure/Media/BumperRenderBackgroundService.cs index fdd7a71..e46aaea 100644 --- a/backend/src/TeleWave.Infrastructure/Media/BumperRenderBackgroundService.cs +++ b/backend/src/TeleWave.Infrastructure/Media/BumperRenderBackgroundService.cs @@ -122,7 +122,11 @@ public sealed class BumperRenderBackgroundService( var spec = await BuildSpecAsync(db, assetId, cancellationToken); if (spec is null) { - await FailAsync(assetId, "Не удалось восстановить спецификацию заставки", cancellationToken); + await FailAsync( + assetId, + "Не удалось восстановить спецификацию заставки", + cancellationToken + ); return; } @@ -195,7 +199,11 @@ public sealed class BumperRenderBackgroundService( if (variant.Kind == Domain.Broadcast.BumperTextKind.NowNext) posterPath = await ResolveShowPosterAsync(db, cache.ToShowId, cancellationToken); - var bgPath = await ResolveTemplateBackgroundAsync(db, template.BackgroundImageId, cancellationToken); + var bgPath = await ResolveTemplateBackgroundAsync( + db, + template.BackgroundImageId, + cancellationToken + ); var aligned = BumperDuration.Aligned( BumperDuration.TemplateSeconds(template), _segmentSeconds diff --git a/backend/src/TeleWave.Infrastructure/Media/BumperRenderQueue.cs b/backend/src/TeleWave.Infrastructure/Media/BumperRenderQueue.cs index a978074..1da262c 100644 --- a/backend/src/TeleWave.Infrastructure/Media/BumperRenderQueue.cs +++ b/backend/src/TeleWave.Infrastructure/Media/BumperRenderQueue.cs @@ -1,20 +1,6 @@ -using System.Threading.Channels; using TeleWave.Application.Common.Interfaces; namespace TeleWave.Infrastructure.Media; -/// Сигнальная очередь-будильник поверх Channel (id ассета — лишь сигнал; работу берём из БД). -public sealed class BumperRenderQueue : IBumperRenderQueue -{ - private readonly Channel _channel = Channel.CreateUnbounded( - new UnboundedChannelOptions { SingleReader = true } - ); - - public void Enqueue(Guid assetId) => _channel.Writer.TryWrite(assetId); - - public async ValueTask WaitAsync(CancellationToken cancellationToken) - { - await _channel.Reader.ReadAsync(cancellationToken); - while (_channel.Reader.TryRead(out _)) { } - } -} +/// Сигнальная очередь-будильник рендерера заставок (см. ). +public sealed class BumperRenderQueue : SignalQueue, IBumperRenderQueue { } diff --git a/backend/src/TeleWave.Infrastructure/Media/FfmpegBumperRenderer.cs b/backend/src/TeleWave.Infrastructure/Media/FfmpegBumperRenderer.cs index 8faa877..5a07854 100644 --- a/backend/src/TeleWave.Infrastructure/Media/FfmpegBumperRenderer.cs +++ b/backend/src/TeleWave.Infrastructure/Media/FfmpegBumperRenderer.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Text; using Microsoft.Extensions.Options; using TeleWave.Application.Common.Interfaces; +using static TeleWave.Infrastructure.Media.FfmpegText; namespace TeleWave.Infrastructure.Media; @@ -221,9 +222,7 @@ public sealed class FfmpegBumperRenderer( var nextSize = FitSize(spec.NextTitle, titleSize, textWidth); vchain .Append(',') - .Append( - DrawLabel(font, nowLabelFile, spec.AccentColor, labelSize, nowLabelY, 0.2) - ); + .Append(DrawLabel(font, nowLabelFile, spec.AccentColor, labelSize, nowLabelY, 0.2)); vchain .Append(',') .Append(DrawTitle(font, nowFile, spec.TextColor, nowSize, nowTitleY, 0.3)); @@ -340,9 +339,6 @@ public sealed class FfmpegBumperRenderer( /// двоеточие экранируется). На Linux (контейнере) — фактически no-op. private static string EscapePath(string path) => path.Replace('\\', '/').Replace(":", "\\:"); - private static string Fmt(double value) => - value.ToString("0.###", CultureInfo.InvariantCulture); - private static void TryDelete(string path) { try @@ -355,11 +351,4 @@ public sealed class FfmpegBumperRenderer( // Файл-подсказка для drawtext; не критично, если не удалился. } } - - private static string Tail(string text) - { - text = text.Trim(); - const int max = 500; - return text.Length <= max ? text : text[^max..]; - } } diff --git a/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs b/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs index 2a94b13..dab5d05 100644 --- a/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs +++ b/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Media; +using static TeleWave.Infrastructure.Media.FfmpegText; namespace TeleWave.Infrastructure.Media; @@ -98,14 +99,7 @@ public sealed class FfmpegMediaProcessor( if (padding) vfilter += $",tpad=stop_duration={Fmt(pad)}:stop_mode=add:color=black"; - var args = new List - { - "-hide_banner", - "-nostdin", - "-y", - "-i", - input, - }; + var args = new List { "-hide_banner", "-nostdin", "-y", "-i", input }; // Явный выбор дорожки по предпочитаемому языку: маппим видео + конкретную аудиодорожку. // Без выбора (audioTrack == null) — не маппим, оставляя дефолтную эвристику ffmpeg (как раньше). @@ -262,13 +256,17 @@ public sealed class FfmpegMediaProcessor( await using var scope = scopeFactory.CreateAsyncScope(); var settings = scope.ServiceProvider.GetRequiredService(); var configured = await settings.GetPreferredAudioLanguagesAsync(cancellationToken); - var raw = string.IsNullOrWhiteSpace(configured) ? _media.PreferredAudioLanguages : configured; - return raw.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + var raw = string.IsNullOrWhiteSpace(configured) + ? _media.PreferredAudioLanguages + : configured; + return raw.Split( + ',', + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries + ) .Select(x => x.ToLowerInvariant()) .ToList(); } - private static (int Width, int Height) ScaleDown(int width, int height) { if (width <= MaxWidth) @@ -279,16 +277,6 @@ public sealed class FfmpegMediaProcessor( return (MaxWidth, scaledHeight); } - private static string Fmt(double value) => - value.ToString("0.###", CultureInfo.InvariantCulture); - - private static string Tail(string text) - { - text = text.Trim(); - const int max = 500; - return text.Length <= max ? text : text[^max..]; - } - private sealed record ProbeInfo( double Duration, int Width, diff --git a/backend/src/TeleWave.Infrastructure/Media/FfmpegText.cs b/backend/src/TeleWave.Infrastructure/Media/FfmpegText.cs new file mode 100644 index 0000000..53b65b7 --- /dev/null +++ b/backend/src/TeleWave.Infrastructure/Media/FfmpegText.cs @@ -0,0 +1,18 @@ +using System.Globalization; + +namespace TeleWave.Infrastructure.Media; + +/// Общие текстовые хелперы для сборки аргументов ffmpeg и разбора его вывода. +internal static class FfmpegText +{ + /// Формат double для фильтров/аргументов ffmpeg (инвариантная культура, до 3 знаков). + public static string Fmt(double value) => value.ToString("0.###", CultureInfo.InvariantCulture); + + /// Хвост stderr для сообщения об ошибке (последние 500 символов). + public static string Tail(string text) + { + text = text.Trim(); + const int max = 500; + return text.Length <= max ? text : text[^max..]; + } +} diff --git a/backend/src/TeleWave.Infrastructure/Media/MediaPathResolver.cs b/backend/src/TeleWave.Infrastructure/Media/MediaPathResolver.cs index 877448b..1b4622f 100644 --- a/backend/src/TeleWave.Infrastructure/Media/MediaPathResolver.cs +++ b/backend/src/TeleWave.Infrastructure/Media/MediaPathResolver.cs @@ -18,7 +18,6 @@ public sealed class MediaPathResolver OriginalsDir = Path.Combine(_root, "originals"); AssetsDir = Path.Combine(_root, "assets"); BumpersDir = Path.Combine(_root, "bumpers"); - MetadataDir = Path.Combine(_root, "metadata"); ImagesDir = Path.Combine(_root, "images"); } @@ -30,9 +29,6 @@ public sealed class MediaPathResolver /// Сырые файлы шаблонов заставок (фон/музыка) по каналам — не режутся на HLS. public string BumpersDir { get; } - /// Картинки метаданных (постеры/кадры), скачанные локально. - public string MetadataDir { get; } - /// Общий реестр изображений (галерея): файлы images/{imageId}{ext}. public string ImagesDir { get; } @@ -43,47 +39,9 @@ public sealed class MediaPathResolver Directory.CreateDirectory(OriginalsDir); Directory.CreateDirectory(AssetsDir); Directory.CreateDirectory(BumpersDir); - Directory.CreateDirectory(MetadataDir); Directory.CreateDirectory(ImagesDir); } - public string MetadataShowDir(Guid showId) => - EnsureWithinRoot(Path.Combine(MetadataDir, "shows", showId.ToString("N"))); - - /// Абсолютный путь к файлу постера шоу (extension — с точкой). - public string MetadataShowPosterPath(Guid showId, string extension) => - EnsureWithinRoot( - Path.Combine(MetadataDir, "shows", showId.ToString("N"), "poster" + extension) - ); - - /// Относительный путь постера от корня (для хранения в БД и отдачи). - public string MetadataShowPosterRelative(Guid showId, string extension) => - $"metadata/shows/{showId:N}/poster{extension}"; - - public string MetadataEpisodeDir(Guid episodeId) => - EnsureWithinRoot(Path.Combine(MetadataDir, "episodes", episodeId.ToString("N"))); - - public string MetadataEpisodeStillPath(Guid episodeId, string extension) => - EnsureWithinRoot( - Path.Combine(MetadataDir, "episodes", episodeId.ToString("N"), "still" + extension) - ); - - public string MetadataEpisodeStillRelative(Guid episodeId, string extension) => - $"metadata/episodes/{episodeId:N}/still{extension}"; - - /// Абсолютный путь по относительному (с защитой от traversal) или null, если вне корня. - public string? ResolveRelative(string relativePath) - { - try - { - return EnsureWithinRoot(Path.Combine(_root, relativePath)); - } - catch (UnauthorizedAccessException) - { - return null; - } - } - public string BumperTemplateDir(Guid templateId) => EnsureWithinRoot(Path.Combine(BumpersDir, templateId.ToString("N"))); diff --git a/backend/src/TeleWave.Infrastructure/Media/MediaProcessingQueue.cs b/backend/src/TeleWave.Infrastructure/Media/MediaProcessingQueue.cs index 2fb6f6d..d75f22d 100644 --- a/backend/src/TeleWave.Infrastructure/Media/MediaProcessingQueue.cs +++ b/backend/src/TeleWave.Infrastructure/Media/MediaProcessingQueue.cs @@ -1,21 +1,6 @@ -using System.Threading.Channels; using TeleWave.Application.Common.Interfaces; namespace TeleWave.Infrastructure.Media; -/// Сигнальная очередь-будильник поверх Channel (id ассета используется лишь как сигнал). -public sealed class MediaProcessingQueue : IMediaProcessingQueue -{ - private readonly Channel _channel = Channel.CreateUnbounded( - new UnboundedChannelOptions { SingleReader = true } - ); - - public void Enqueue(Guid assetId) => _channel.Writer.TryWrite(assetId); - - public async ValueTask WaitAsync(CancellationToken cancellationToken) - { - await _channel.Reader.ReadAsync(cancellationToken); - // Сдренировать накопившиеся сигналы — работу всё равно берём из БД пачкой. - while (_channel.Reader.TryRead(out _)) { } - } -} +/// Сигнальная очередь-будильник медиа-конвейера (см. ). +public sealed class MediaProcessingQueue : SignalQueue, IMediaProcessingQueue { } diff --git a/backend/src/TeleWave.Infrastructure/Media/ProcessRunner.cs b/backend/src/TeleWave.Infrastructure/Media/ProcessRunner.cs index c8611cb..32fce21 100644 --- a/backend/src/TeleWave.Infrastructure/Media/ProcessRunner.cs +++ b/backend/src/TeleWave.Infrastructure/Media/ProcessRunner.cs @@ -63,13 +63,9 @@ internal static class ProcessRunner // (битый источник, -stream_loop и т.п.) не должен держать слот параллелизма/тик планировщика вечно. using var timeoutCts = timeout > TimeSpan.Zero ? new CancellationTokenSource(timeout) : null; - using var linked = - timeoutCts is null - ? null - : CancellationTokenSource.CreateLinkedTokenSource( - cancellationToken, - timeoutCts.Token - ); + using var linked = timeoutCts is null + ? null + : CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); var waitToken = linked?.Token ?? cancellationToken; try @@ -79,7 +75,10 @@ internal static class ProcessRunner catch (OperationCanceledException) { TryKill(process); - if (timeoutCts is { IsCancellationRequested: true } && !cancellationToken.IsCancellationRequested) + if ( + timeoutCts is { IsCancellationRequested: true } + && !cancellationToken.IsCancellationRequested + ) throw new TimeoutException( $"Процесс {fileName} превысил таймаут {timeout.TotalSeconds:0}с и был прерван." ); diff --git a/backend/src/TeleWave.Infrastructure/Media/SignalQueue.cs b/backend/src/TeleWave.Infrastructure/Media/SignalQueue.cs new file mode 100644 index 0000000..dd09e51 --- /dev/null +++ b/backend/src/TeleWave.Infrastructure/Media/SignalQueue.cs @@ -0,0 +1,26 @@ +using System.Threading.Channels; + +namespace TeleWave.Infrastructure.Media; + +/// +/// Базовая сигнальная очередь-будильник поверх Channel: id ассета используется лишь как сигнал, +/// а работу фоновый обработчик всё равно берёт из БД пачкой (по статусу Pending). Поэтому потеря +/// сигнала при рестарте не теряет задачи — они подхватываются из базы. +/// +public abstract class SignalQueue +{ + private readonly Channel _channel = Channel.CreateUnbounded( + new UnboundedChannelOptions { SingleReader = true } + ); + + /// Разбудить обработчик: появился ассет в статусе Pending. + public void Enqueue(Guid assetId) => _channel.Writer.TryWrite(assetId); + + /// Ждать сигнала о новой работе (с дренажом накопленных). + public async ValueTask WaitAsync(CancellationToken cancellationToken) + { + await _channel.Reader.ReadAsync(cancellationToken); + // Сдренировать накопившиеся сигналы — работу всё равно берём из БД пачкой. + while (_channel.Reader.TryRead(out _)) { } + } +} diff --git a/backend/src/TeleWave.Infrastructure/Metadata/MetadataJson.cs b/backend/src/TeleWave.Infrastructure/Metadata/MetadataJson.cs new file mode 100644 index 0000000..ddd5f7f --- /dev/null +++ b/backend/src/TeleWave.Infrastructure/Metadata/MetadataJson.cs @@ -0,0 +1,53 @@ +using System.Text.Json; + +namespace TeleWave.Infrastructure.Metadata; + +/// Общие для провайдеров метаданных хелперы: HTTP-загрузка JSON и чтение полей. +internal static class MetadataJson +{ + /// GET+parse через клиент "metadata"; бросает при не-2xx/сетевой ошибке (для поиска — показать сбой). + public static async Task GetAsync( + IHttpClientFactory httpFactory, + string url, + CancellationToken cancellationToken + ) + { + var client = httpFactory.CreateClient("metadata"); + using var response = await client.GetAsync(url, cancellationToken); + response.EnsureSuccessStatusCode(); + var stream = await response.Content.ReadAsStreamAsync(cancellationToken); + return await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); + } + + /// Как , но глотает ошибки в null (для get/episode — деградируем мягко). + public static async Task TryGetAsync( + IHttpClientFactory httpFactory, + string url, + CancellationToken cancellationToken + ) + { + try + { + return await GetAsync(httpFactory, url, cancellationToken); + } + catch (Exception ex) + when (ex is HttpRequestException or JsonException or TaskCanceledException) + { + return null; + } + } + + public static string? GetString(JsonElement el, string name) => + el.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String + ? v.GetString() + : null; + + public static int? GetInt(JsonElement el, string name) => + el.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.Number + ? v.GetInt32() + : null; + + /// Год из первых 4 символов строки даты/года ("YYYY-MM-DD" или "YYYY"). + public static int? YearFrom(string? value) => + value is { Length: >= 4 } && int.TryParse(value.AsSpan(0, 4), out var y) ? y : null; +} diff --git a/backend/src/TeleWave.Infrastructure/Metadata/OmdbMetadataProvider.cs b/backend/src/TeleWave.Infrastructure/Metadata/OmdbMetadataProvider.cs index b7f22bc..3956cc0 100644 --- a/backend/src/TeleWave.Infrastructure/Metadata/OmdbMetadataProvider.cs +++ b/backend/src/TeleWave.Infrastructure/Metadata/OmdbMetadataProvider.cs @@ -3,6 +3,7 @@ using System.Text.Json; using Microsoft.Extensions.Options; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Metadata; +using static TeleWave.Infrastructure.Metadata.MetadataJson; namespace TeleWave.Infrastructure.Metadata; @@ -23,7 +24,7 @@ public sealed class OmdbMetadataProvider( { var url = $"{_omdb.BaseUrl}/?apikey={_omdb.ApiKey}&type=series&s={Uri.EscapeDataString(query)}"; - using var doc = await GetJsonAsync(url, cancellationToken); + using var doc = await GetAsync(httpFactory, url, cancellationToken); if (!doc.RootElement.TryGetProperty("Search", out var search)) return []; @@ -52,7 +53,7 @@ public sealed class OmdbMetadataProvider( ) { var url = $"{_omdb.BaseUrl}/?apikey={_omdb.ApiKey}&i={Uri.EscapeDataString(externalId)}"; - using var doc = await TryGetJsonAsync(url, cancellationToken); + using var doc = await TryGetAsync(httpFactory, url, cancellationToken); if (doc is null || !IsResponseTrue(doc.RootElement)) return null; var root = doc.RootElement; @@ -75,7 +76,7 @@ public sealed class OmdbMetadataProvider( var url = $"{_omdb.BaseUrl}/?apikey={_omdb.ApiKey}&i={Uri.EscapeDataString(externalId)}" + $"&Season={season}&Episode={episode}"; - using var doc = await TryGetJsonAsync(url, cancellationToken); + using var doc = await TryGetAsync(httpFactory, url, cancellationToken); if (doc is null || !IsResponseTrue(doc.RootElement)) return null; var root = doc.RootElement; @@ -95,7 +96,7 @@ public sealed class OmdbMetadataProvider( { var url = $"{_omdb.BaseUrl}/?apikey={_omdb.ApiKey}&i={Uri.EscapeDataString(externalId)}&Season={season}"; - using var doc = await TryGetJsonAsync(url, cancellationToken); + using var doc = await TryGetAsync(httpFactory, url, cancellationToken); if ( doc is null || !IsResponseTrue(doc.RootElement) @@ -106,53 +107,14 @@ public sealed class OmdbMetadataProvider( return episodes.GetArrayLength(); } - /// GET+parse; бросает при не-2xx/сетевой ошибке (для поиска — чтобы показать сбой). - private async Task GetJsonAsync(string url, CancellationToken cancellationToken) - { - var client = httpFactory.CreateClient("metadata"); - using var response = await client.GetAsync(url, cancellationToken); - response.EnsureSuccessStatusCode(); - var stream = await response.Content.ReadAsStreamAsync(cancellationToken); - return await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); - } - - /// Как GetJsonAsync, но глотает ошибки в null (для get/episode — деградируем мягко). - private async Task TryGetJsonAsync( - string url, - CancellationToken cancellationToken - ) - { - try - { - return await GetJsonAsync(url, cancellationToken); - } - catch (Exception ex) - when (ex is HttpRequestException or JsonException or TaskCanceledException) - { - return null; - } - } - private static bool IsResponseTrue(JsonElement root) => GetString(root, "Response") is { } r && r.Equals("True", StringComparison.OrdinalIgnoreCase); - private static string? GetString(JsonElement el, string name) => - el.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String - ? v.GetString() - : null; - /// OMDb отдаёт «N/A» вместо отсутствующих значений — приводим к null. private static string? Clean(string? value) => string.IsNullOrWhiteSpace(value) || value == "N/A" ? null : value; - private static int? YearFrom(string? year) - { - if (year is { Length: >= 4 } && int.TryParse(year.AsSpan(0, 4), out var y)) - return y; - return null; - } - private static DateOnly? DateFrom(string? released) => DateTime.TryParse(released, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? DateOnly.FromDateTime(dt) diff --git a/backend/src/TeleWave.Infrastructure/Metadata/TmdbMetadataProvider.cs b/backend/src/TeleWave.Infrastructure/Metadata/TmdbMetadataProvider.cs index 1a6c3ce..7f529ff 100644 --- a/backend/src/TeleWave.Infrastructure/Metadata/TmdbMetadataProvider.cs +++ b/backend/src/TeleWave.Infrastructure/Metadata/TmdbMetadataProvider.cs @@ -3,6 +3,7 @@ using System.Text.Json; using Microsoft.Extensions.Options; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Metadata; +using static TeleWave.Infrastructure.Metadata.MetadataJson; namespace TeleWave.Infrastructure.Metadata; @@ -26,7 +27,7 @@ public sealed class TmdbMetadataProvider( var url = $"{Tmdb.BaseUrl}/search/tv?api_key={Tmdb.ApiKey}&language={_options.Language}" + $"&include_adult=false&query={Uri.EscapeDataString(query)}"; - using var doc = await GetJsonAsync(url, cancellationToken); + using var doc = await GetAsync(httpFactory, url, cancellationToken); if (!doc.RootElement.TryGetProperty("results", out var results)) return []; @@ -56,7 +57,7 @@ public sealed class TmdbMetadataProvider( { var url = $"{Tmdb.BaseUrl}/tv/{externalId}?api_key={Tmdb.ApiKey}&language={_options.Language}"; - using var doc = await TryGetJsonAsync(url, cancellationToken); + using var doc = await TryGetAsync(httpFactory, url, cancellationToken); if (doc is null) return null; var root = doc.RootElement; @@ -79,7 +80,7 @@ public sealed class TmdbMetadataProvider( var url = $"{Tmdb.BaseUrl}/tv/{externalId}/season/{season}/episode/{episode}" + $"?api_key={Tmdb.ApiKey}&language={_options.Language}"; - using var doc = await TryGetJsonAsync(url, cancellationToken); + using var doc = await TryGetAsync(httpFactory, url, cancellationToken); if (doc is null) return null; var root = doc.RootElement; @@ -100,7 +101,7 @@ public sealed class TmdbMetadataProvider( var url = $"{Tmdb.BaseUrl}/tv/{externalId}/season/{season}" + $"?api_key={Tmdb.ApiKey}&language={_options.Language}"; - using var doc = await TryGetJsonAsync(url, cancellationToken); + using var doc = await TryGetAsync(httpFactory, url, cancellationToken); if ( doc is null || !doc.RootElement.TryGetProperty("episodes", out var episodes) @@ -116,46 +117,6 @@ public sealed class TmdbMetadataProvider( private string? StillUrl(string? path) => string.IsNullOrEmpty(path) ? null : $"{Tmdb.ImageBaseUrl}/{Tmdb.StillSize}{path}"; - /// GET+parse; бросает при не-2xx/сетевой ошибке (для поиска — чтобы показать сбой). - private async Task GetJsonAsync(string url, CancellationToken cancellationToken) - { - var client = httpFactory.CreateClient("metadata"); - using var response = await client.GetAsync(url, cancellationToken); - response.EnsureSuccessStatusCode(); - var stream = await response.Content.ReadAsStreamAsync(cancellationToken); - return await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); - } - - /// Как GetJsonAsync, но глотает ошибки в null (для get/episode — деградируем мягко). - private async Task TryGetJsonAsync( - string url, - CancellationToken cancellationToken - ) - { - try - { - return await GetJsonAsync(url, cancellationToken); - } - catch (Exception ex) - when (ex is HttpRequestException or JsonException or TaskCanceledException) - { - return null; - } - } - - private static string? GetString(JsonElement el, string name) => - el.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String - ? v.GetString() - : null; - - private static int? GetInt(JsonElement el, string name) => - el.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.Number - ? v.GetInt32() - : null; - - private static int? YearFrom(string? date) => - date is { Length: >= 4 } && int.TryParse(date.AsSpan(0, 4), out var y) ? y : null; - private static DateOnly? DateFrom(string? date) => DateOnly.TryParse(date, CultureInfo.InvariantCulture, out var d) ? d : null; } diff --git a/backend/src/TeleWave.Infrastructure/Migrations/20260725202904_BumperAssetRenderContext.cs b/backend/src/TeleWave.Infrastructure/Migrations/20260725202904_BumperAssetRenderContext.cs index e70c926..003ec37 100644 --- a/backend/src/TeleWave.Infrastructure/Migrations/20260725202904_BumperAssetRenderContext.cs +++ b/backend/src/TeleWave.Infrastructure/Migrations/20260725202904_BumperAssetRenderContext.cs @@ -16,37 +16,34 @@ namespace TeleWave.Infrastructure.Migrations table: "BumperAssets", type: "uuid", nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + defaultValue: new Guid("00000000-0000-0000-0000-000000000000") + ); migrationBuilder.AddColumn( name: "TemplateId", table: "BumperAssets", type: "uuid", nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + defaultValue: new Guid("00000000-0000-0000-0000-000000000000") + ); migrationBuilder.AddColumn( name: "VariantId", table: "BumperAssets", type: "uuid", nullable: false, - defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + defaultValue: new Guid("00000000-0000-0000-0000-000000000000") + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "ChannelId", - table: "BumperAssets"); + migrationBuilder.DropColumn(name: "ChannelId", table: "BumperAssets"); - migrationBuilder.DropColumn( - name: "TemplateId", - table: "BumperAssets"); + migrationBuilder.DropColumn(name: "TemplateId", table: "BumperAssets"); - migrationBuilder.DropColumn( - name: "VariantId", - table: "BumperAssets"); + migrationBuilder.DropColumn(name: "VariantId", table: "BumperAssets"); } } } diff --git a/backend/src/TeleWave.Infrastructure/Migrations/20260725210453_MediaProcessingTiming.cs b/backend/src/TeleWave.Infrastructure/Migrations/20260725210453_MediaProcessingTiming.cs index c57b8a5..3ac8b0e 100644 --- a/backend/src/TeleWave.Infrastructure/Migrations/20260725210453_MediaProcessingTiming.cs +++ b/backend/src/TeleWave.Infrastructure/Migrations/20260725210453_MediaProcessingTiming.cs @@ -15,25 +15,23 @@ namespace TeleWave.Infrastructure.Migrations name: "ProcessingDuration", table: "MediaAssets", type: "interval", - nullable: true); + nullable: true + ); migrationBuilder.AddColumn( name: "ProcessingStartedAt", table: "MediaAssets", type: "timestamp with time zone", - nullable: true); + nullable: true + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "ProcessingDuration", - table: "MediaAssets"); + migrationBuilder.DropColumn(name: "ProcessingDuration", table: "MediaAssets"); - migrationBuilder.DropColumn( - name: "ProcessingStartedAt", - table: "MediaAssets"); + migrationBuilder.DropColumn(name: "ProcessingStartedAt", table: "MediaAssets"); } } } diff --git a/backend/src/TeleWave.Infrastructure/Migrations/20260725225737_ShowAudience.cs b/backend/src/TeleWave.Infrastructure/Migrations/20260725225737_ShowAudience.cs index 7ef487d..6f89a55 100644 --- a/backend/src/TeleWave.Infrastructure/Migrations/20260725225737_ShowAudience.cs +++ b/backend/src/TeleWave.Infrastructure/Migrations/20260725225737_ShowAudience.cs @@ -15,15 +15,14 @@ namespace TeleWave.Infrastructure.Migrations table: "Shows", type: "integer", nullable: false, - defaultValue: 0); + defaultValue: 0 + ); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "Audience", - table: "Shows"); + migrationBuilder.DropColumn(name: "Audience", table: "Shows"); } } } diff --git a/backend/src/TeleWave.Infrastructure/Persistence/AppDbContext.cs b/backend/src/TeleWave.Infrastructure/Persistence/AppDbContext.cs index dc6077e..3ce8eb7 100644 --- a/backend/src/TeleWave.Infrastructure/Persistence/AppDbContext.cs +++ b/backend/src/TeleWave.Infrastructure/Persistence/AppDbContext.cs @@ -40,10 +40,7 @@ public class AppDbContext(DbContextOptions options) // Ключ — стабильный int64 из GUID канала; коллизии между каналами лишь сериализуют их генерацию, // корректности не нарушают. var key = BitConverter.ToInt64(channelId.ToByteArray()); - return Database.ExecuteSqlAsync( - $"SELECT pg_advisory_xact_lock({key})", - cancellationToken - ); + return Database.ExecuteSqlAsync($"SELECT pg_advisory_xact_lock({key})", cancellationToken); } protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/backend/src/TeleWave.Infrastructure/Persistence/MigrationExtensions.cs b/backend/src/TeleWave.Infrastructure/Persistence/MigrationExtensions.cs index f161f75..930094a 100644 --- a/backend/src/TeleWave.Infrastructure/Persistence/MigrationExtensions.cs +++ b/backend/src/TeleWave.Infrastructure/Persistence/MigrationExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using TeleWave.Infrastructure.Media; namespace TeleWave.Infrastructure.Persistence; @@ -16,99 +15,4 @@ public static class MigrationExtensions var dbContext = scope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(cancellationToken); } - - /// - /// Идемпотентно переносит файлы постеров шоу, мигрированных в реестр изображений, из старого - /// расположения metadata/shows/{showId}/poster{ext} в images/{imageId}{ext}. Безопасно к повторным - /// запускам (пропускает, если целевой файл уже на месте). - /// - public static async Task RelocateLegacyImagesAsync( - this IServiceProvider services, - CancellationToken cancellationToken = default - ) - { - await using var scope = services.CreateAsyncScope(); - var dbContext = scope.ServiceProvider.GetRequiredService(); - var paths = scope.ServiceProvider.GetRequiredService(); - - Directory.CreateDirectory(paths.ImagesDir); - - // Постеры шоу: metadata/shows/{showId}/poster{ext} → images/{imageId}{ext}. - var posters = await dbContext - .Shows.AsNoTracking() - .Where(s => s.PosterImageId != null) - .Join( - dbContext.Images, - s => s.PosterImageId, - i => i.Id, - (s, i) => - new - { - EntityId = s.Id, - ImageId = i.Id, - i.FileExtension, - } - ) - .ToListAsync(cancellationToken); - foreach (var p in posters) - Relocate( - paths.ImagePath(p.ImageId, p.FileExtension), - paths.MetadataShowPosterPath(p.EntityId, p.FileExtension) - ); - - // Кадры серий: metadata/episodes/{episodeId}/still{ext} → images/{imageId}{ext}. - var stills = await dbContext - .Shows.AsNoTracking() - .SelectMany(s => s.Episodes) - .Where(e => e.StillImageId != null) - .Join( - dbContext.Images, - e => e.StillImageId, - i => i.Id, - (e, i) => - new - { - EntityId = e.Id, - ImageId = i.Id, - i.FileExtension, - } - ) - .ToListAsync(cancellationToken); - foreach (var s in stills) - Relocate( - paths.ImagePath(s.ImageId, s.FileExtension), - paths.MetadataEpisodeStillPath(s.EntityId, s.FileExtension) - ); - - // Фоны блоков заставок: bumpers/{templateId}/background{ext} → images/{imageId}{ext}. - var backgrounds = await dbContext - .Channels.AsNoTracking() - .SelectMany(c => c.BumperTemplates) - .Where(t => t.BackgroundImageId != null) - .Join( - dbContext.Images, - t => t.BackgroundImageId, - i => i.Id, - (t, i) => - new - { - EntityId = t.Id, - ImageId = i.Id, - i.FileExtension, - } - ) - .ToListAsync(cancellationToken); - foreach (var b in backgrounds) - Relocate( - paths.ImagePath(b.ImageId, b.FileExtension), - paths.BumperTemplateFilePath(b.EntityId, "background", b.FileExtension) - ); - - static void Relocate(string target, string legacy) - { - if (File.Exists(target) || !File.Exists(legacy)) - return; - File.Move(legacy, target); - } - } } diff --git a/backend/src/TeleWave.Infrastructure/Settings/SiteSettings.cs b/backend/src/TeleWave.Infrastructure/Settings/SiteSettings.cs index a968805..c010fac 100644 --- a/backend/src/TeleWave.Infrastructure/Settings/SiteSettings.cs +++ b/backend/src/TeleWave.Infrastructure/Settings/SiteSettings.cs @@ -14,14 +14,20 @@ public sealed class SiteSettings(IAppDbContext dbContext) : ISiteSettings public async Task SetRegistrationEnabledAsync(bool enabled, CancellationToken cancellationToken) { - await UpsertAsync(SettingKeys.RegistrationEnabled, enabled ? "true" : "false", cancellationToken); + await UpsertAsync( + SettingKeys.RegistrationEnabled, + enabled ? "true" : "false", + cancellationToken + ); } public Task GetPreferredAudioLanguagesAsync(CancellationToken cancellationToken) => dbContext.GetStringSettingAsync(SettingKeys.PreferredAudioLanguages, "", cancellationToken); - public Task SetPreferredAudioLanguagesAsync(string value, CancellationToken cancellationToken) => - UpsertAsync(SettingKeys.PreferredAudioLanguages, value, cancellationToken); + public Task SetPreferredAudioLanguagesAsync( + string value, + CancellationToken cancellationToken + ) => UpsertAsync(SettingKeys.PreferredAudioLanguages, value, cancellationToken); private async Task UpsertAsync(string key, string value, CancellationToken cancellationToken) { diff --git a/backend/tests/TeleWave.Application.Tests/Auth/LoginCommandHandlerTests.cs b/backend/tests/TeleWave.Application.Tests/Auth/LoginCommandHandlerTests.cs index 155dc34..ade2de7 100644 --- a/backend/tests/TeleWave.Application.Tests/Auth/LoginCommandHandlerTests.cs +++ b/backend/tests/TeleWave.Application.Tests/Auth/LoginCommandHandlerTests.cs @@ -26,9 +26,7 @@ public class LoginCommandHandlerTests .Returns(Result.Success(new AuthenticatedUser(userId, "alice", "user"))); _identityService .GetProfileAsync(userId, Arg.Any()) - .Returns( - new CurrentUserProfile(userId, "alice", Guid.NewGuid(), "user", IsBlocked: false) - ); + .Returns(new CurrentUserProfile(userId, "alice", "user", IsBlocked: false)); _jwtTokenService .GenerateAccessToken(Arg.Any()) .Returns(("access-token", DateTimeOffset.UtcNow.AddMinutes(15))); @@ -67,9 +65,7 @@ public class LoginCommandHandlerTests .Returns(Result.Success(new AuthenticatedUser(userId, "alice", "user"))); _identityService .GetProfileAsync(userId, Arg.Any()) - .Returns( - new CurrentUserProfile(userId, "alice", Guid.NewGuid(), "user", IsBlocked: true) - ); + .Returns(new CurrentUserProfile(userId, "alice", "user", IsBlocked: true)); var result = await CreateHandler() .Handle(new LoginCommand("alice", "password123"), CancellationToken.None); diff --git a/backend/tests/TeleWave.Application.Tests/Auth/RegisterCommandHandlerTests.cs b/backend/tests/TeleWave.Application.Tests/Auth/RegisterCommandHandlerTests.cs index 91fa9de..60b2bf2 100644 --- a/backend/tests/TeleWave.Application.Tests/Auth/RegisterCommandHandlerTests.cs +++ b/backend/tests/TeleWave.Application.Tests/Auth/RegisterCommandHandlerTests.cs @@ -32,9 +32,7 @@ public class RegisterCommandHandlerTests .Returns(Result.Success(userId)); _identityService .GetProfileAsync(userId, Arg.Any()) - .Returns( - new CurrentUserProfile(userId, "bob", Guid.NewGuid(), "user", IsBlocked: false) - ); + .Returns(new CurrentUserProfile(userId, "bob", "user", IsBlocked: false)); _jwtTokenService .GenerateAccessToken(Arg.Any()) .Returns(("access-token", DateTimeOffset.UtcNow.AddMinutes(15))); diff --git a/backend/tests/TeleWave.Application.Tests/Broadcast/QueryHandlersTests.cs b/backend/tests/TeleWave.Application.Tests/Broadcast/QueryHandlersTests.cs index e171702..1106893 100644 --- a/backend/tests/TeleWave.Application.Tests/Broadcast/QueryHandlersTests.cs +++ b/backend/tests/TeleWave.Application.Tests/Broadcast/QueryHandlersTests.cs @@ -9,7 +9,6 @@ using TeleWave.Application.Broadcast.RemoveChannelAd; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Library.DeleteShow; using TeleWave.Application.Library.GetShow; -using TeleWave.Application.Media.GetMedia; using TeleWave.Application.Tests.Support; using TeleWave.Domain.Broadcast; using TeleWave.Domain.Library; @@ -152,31 +151,6 @@ public class QueryHandlersTests Assert.False(missing.IsSuccess); } - [Fact] - public async Task GetMedia_ReturnsDtoOrNotFound() - { - var fixture = new TestDb(); - var asset = MediaAsset.Register("clip.mp4", ".mp4", MediaSource.Upload); - await using (var seed = fixture.New()) - { - seed.MediaAssets.Add(asset); - await seed.SaveChangesAsync(CancellationToken.None); - } - - await using var db = fixture.New(); - var ok = await new GetMediaAssetQueryHandler(db).Handle( - new GetMediaAssetQuery(asset.Id), - CancellationToken.None - ); - Assert.True(ok.IsSuccess); - - var missing = await new GetMediaAssetQueryHandler(db).Handle( - new GetMediaAssetQuery(Guid.NewGuid()), - CancellationToken.None - ); - Assert.False(missing.IsSuccess); - } - [Fact] public async Task DeleteShow_RemovesOrNotFound() { diff --git a/backend/tests/TeleWave.Application.Tests/Metadata/FindMissingEpisodesTests.cs b/backend/tests/TeleWave.Application.Tests/Metadata/FindMissingEpisodesTests.cs index f4fd58b..7498cac 100644 --- a/backend/tests/TeleWave.Application.Tests/Metadata/FindMissingEpisodesTests.cs +++ b/backend/tests/TeleWave.Application.Tests/Metadata/FindMissingEpisodesTests.cs @@ -30,12 +30,8 @@ public class FindMissingEpisodesTests } var provider = Substitute.For(); - provider - .GetSeasonEpisodeCountAsync("123", 1, Arg.Any()) - .Returns(5); - provider - .GetSeasonEpisodeCountAsync("123", 2, Arg.Any()) - .Returns(3); + provider.GetSeasonEpisodeCountAsync("123", 1, Arg.Any()).Returns(5); + provider.GetSeasonEpisodeCountAsync("123", 2, Arg.Any()).Returns(3); var resolver = Substitute.For(); resolver.Resolve("tmdb").Returns(provider); diff --git a/backend/tests/TeleWave.Integration.Tests/PostgresFixture.cs b/backend/tests/TeleWave.Integration.Tests/PostgresFixture.cs index 1b0a753..910abc1 100644 --- a/backend/tests/TeleWave.Integration.Tests/PostgresFixture.cs +++ b/backend/tests/TeleWave.Integration.Tests/PostgresFixture.cs @@ -47,7 +47,8 @@ public sealed class PostgresFixture : IAsyncLifetime public sealed class PostgresCollection : ICollectionFixture; /// Детерминированный источник случайности для планировщика в тестах. -internal sealed class SequenceRandom(params int[] sequence) : Domain.Broadcast.Scheduling.IRandomSource +internal sealed class SequenceRandom(params int[] sequence) + : Domain.Broadcast.Scheduling.IRandomSource { private readonly int[] _sequence = sequence.Length == 0 ? [0] : sequence; private int _i; diff --git a/backend/tests/TeleWave.Integration.Tests/TransactionIntegrationTests.cs b/backend/tests/TeleWave.Integration.Tests/TransactionIntegrationTests.cs index 853a63f..82e67f6 100644 --- a/backend/tests/TeleWave.Integration.Tests/TransactionIntegrationTests.cs +++ b/backend/tests/TeleWave.Integration.Tests/TransactionIntegrationTests.cs @@ -74,7 +74,11 @@ public sealed class TransactionIntegrationTests(PostgresFixture fixture) var storage = Substitute.For(); storage - .DeleteAssetArtifactsAsync(Arg.Any(), Arg.Any(), Arg.Any()) + .DeleteAssetArtifactsAsync( + Arg.Any(), + Arg.Any(), + Arg.Any() + ) .Returns(Task.CompletedTask); await using (var db = fixture.CreateContext()) @@ -89,9 +93,7 @@ public sealed class TransactionIntegrationTests(PostgresFixture fixture) await using var verify = fixture.CreateContext(); Assert.False(await verify.MediaAssets.AnyAsync(a => a.Id == asset.Id)); Assert.False(await verify.ScheduleEntries.AnyAsync(e => e.MediaAssetId == asset.Id)); - var reloaded = await verify - .Shows.Include(s => s.Episodes) - .FirstAsync(s => s.Id == show.Id); + var reloaded = await verify.Shows.Include(s => s.Episodes).FirstAsync(s => s.Id == show.Id); Assert.Empty(reloaded.Episodes); await storage .Received() diff --git a/frontend/package.json b/frontend/package.json index 42a3e04..3189c70 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,8 +9,7 @@ "build": "tsc -b && vite build", "lint": "oxlint", "typecheck": "tsc -b", - "preview": "vite preview", - "gen:api": "openapi-typescript http://localhost:8080/openapi/v1.json -o src/shared/api/schema.gen.ts" + "preview": "vite preview" }, "dependencies": { "@hookform/resolvers": "^5.4.0", @@ -40,7 +39,6 @@ "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.3", - "openapi-typescript": "^7.13.0", "oxlint": "^1.71.0", "tailwindcss": "^4.3.2", "typescript": "~6.0.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 5bef8e8..81f6d10 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -71,7 +71,7 @@ importers: version: 4.3.3(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0)) '@tanstack/router-plugin': specifier: ^1.168.18 - version: 1.168.23(@tanstack/react-router@1.170.18(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(rolldown@1.1.5)(supports-color@10.2.2)(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0)) + version: 1.168.23(@tanstack/react-router@1.170.18(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0)) '@types/node': specifier: ^24.13.2 version: 24.13.3 @@ -84,9 +84,6 @@ importers: '@vitejs/plugin-react': specifier: ^6.0.3 version: 6.0.4(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0)) - openapi-typescript: - specifier: ^7.13.0 - version: 7.13.0(typescript@6.0.3) oxlint: specifier: ^1.71.0 version: 1.75.0 @@ -631,16 +628,6 @@ packages: '@radix-ui/rect@1.1.3': resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} - '@redocly/ajv@8.11.2': - resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} - - '@redocly/config@0.22.0': - resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} - - '@redocly/openapi-core@1.34.17': - resolution: {integrity: sha512-wsV2keCt6B806XpSdezbWZ9aFJYf14YVh+XQf0ESt7M90yqVuxH9//PxvtC70sgj9OCkRM3nRaLfu4MsGQZRig==} - engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@rolldown/binding-android-arm64@1.1.5': resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -928,21 +915,10 @@ packages: babel-plugin-react-compiler: optional: true - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} @@ -950,17 +926,11 @@ packages: babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.11.1: resolution: {integrity: sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==} engines: {node: '>=6.0.0'} hasBin: true - brace-expansion@2.1.2: - resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} - browserslist@4.28.7: resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -969,9 +939,6 @@ packages: caniuse-lite@1.0.30001806: resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} - change-case@5.4.4: - resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} - chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -983,9 +950,6 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - colorette@1.4.0: - resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1026,9 +990,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1060,10 +1021,6 @@ packages: html-parse-stringify@4.0.1: resolution: {integrity: sha512-0zHsZJrK7S3K2aucXWL6ycoYJ/iNtIcFHC/nYQgFklPtrv5LpJctIiSCroWZWeuoXvuyFdzp6KzjJQ+OT5MfFw==} - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - i18next@26.3.6: resolution: {integrity: sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==} peerDependencies: @@ -1072,10 +1029,6 @@ packages: typescript: optional: true - index-to-position@1.2.0: - resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} - engines: {node: '>=18'} - isbot@5.2.1: resolution: {integrity: sha512-dJ+LpKyClQZ7NG+j3OensC/mAZkGpukE9YUrgPYvAZj2doVL0edfDgywTUh5CXa0o+nW9a1V9e5+CJTX8+SxRw==} engines: {node: '>=18'} @@ -1084,25 +1037,14 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - js-levenshtein@1.1.6: - resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} - engines: {node: '>=0.10.0'} - js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.2.0: - resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} - hasBin: true - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1267,10 +1209,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1283,12 +1221,6 @@ packages: resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} engines: {node: '>=18'} - openapi-typescript@7.13.0: - resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} - hasBin: true - peerDependencies: - typescript: ^5.x - oxlint@1.75.0: resolution: {integrity: sha512-m9WzjRcRYA/uqIZDa9tclrieoPJ/ln1QYTKdFx6NUOs8uY5DiHlIwRQoCrHT6OM6O3ww3l2skY5gO7G7ZphE7g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1302,10 +1234,6 @@ packages: vite-plus: optional: true - parse-json@8.3.0: - resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} - engines: {node: '>=18'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1316,10 +1244,6 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - postcss@8.5.22: resolution: {integrity: sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==} engines: {node: ^10 || ^12 || >=14} @@ -1394,10 +1318,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - rolldown@1.1.5: resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1424,10 +1344,6 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - tailwind-merge@3.6.0: resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} @@ -1445,10 +1361,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -1496,9 +1408,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - uri-js-replace@1.0.1: - resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} - use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -1573,13 +1482,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml-ast-parser@0.0.43: - resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -1611,7 +1513,7 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7(supports-color@10.2.2)': + '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 @@ -1620,11 +1522,11 @@ snapshots: '@babel/helpers': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@10.2.2) + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1651,17 +1553,17 @@ snapshots: '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.7(supports-color@10.2.2) + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@10.2.2) + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -1688,7 +1590,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.7(supports-color@10.2.2)': + '@babel/traverse@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 @@ -1696,7 +1598,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -2083,29 +1985,6 @@ snapshots: '@radix-ui/rect@1.1.3': {} - '@redocly/ajv@8.11.2': - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js-replace: 1.0.1 - - '@redocly/config@0.22.0': {} - - '@redocly/openapi-core@1.34.17(supports-color@10.2.2)': - dependencies: - '@redocly/ajv': 8.11.2 - '@redocly/config': 0.22.0 - colorette: 1.4.0 - https-proxy-agent: 7.0.6(supports-color@10.2.2) - js-levenshtein: 1.1.6 - js-yaml: 4.2.0 - minimatch: 5.1.9 - pluralize: 8.0.0 - yaml-ast-parser: 0.0.43 - transitivePeerDependencies: - - supports-color - '@rolldown/binding-android-arm64@1.1.5': optional: true @@ -2272,9 +2151,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.23(@tanstack/react-router@1.170.18(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(rolldown@1.1.5)(supports-color@10.2.2)(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0))': + '@tanstack/router-plugin@1.168.23(@tanstack/react-router@1.170.18(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(rolldown@1.1.5)(vite@8.1.5(@types/node@24.13.3)(jiti@2.7.0))': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 '@tanstack/router-core': 1.171.15 @@ -2335,35 +2214,23 @@ snapshots: '@rolldown/pluginutils': 1.0.1 vite: 8.1.5(@types/node@24.13.3)(jiti@2.7.0) - agent-base@7.1.4: {} - - ansi-colors@4.1.3: {} - ansis@4.3.1: {} - argparse@2.0.1: {} - aria-hidden@1.2.6: dependencies: tslib: 2.8.1 babel-dead-code-elimination@1.0.12: dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/parser': 7.29.7 - '@babel/traverse': 7.29.7(supports-color@10.2.2) + '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - balanced-match@1.0.2: {} - baseline-browser-mapping@2.11.1: {} - brace-expansion@2.1.2: - dependencies: - balanced-match: 1.0.2 - browserslist@4.28.7: dependencies: baseline-browser-mapping: 2.11.1 @@ -2374,8 +2241,6 @@ snapshots: caniuse-lite@1.0.30001806: {} - change-case@5.4.4: {} - chokidar@5.0.0: dependencies: readdirp: 5.0.0 @@ -2386,19 +2251,15 @@ snapshots: clsx@2.1.1: {} - colorette@1.4.0: {} - convert-source-map@2.0.0: {} cookie-es@3.1.1: {} csstype@3.2.3: {} - debug@4.4.3(supports-color@10.2.2): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 10.2.2 detect-libc@2.1.2: {} @@ -2415,8 +2276,6 @@ snapshots: escalade@3.2.0: {} - fast-deep-equal@3.1.3: {} - fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -2434,35 +2293,18 @@ snapshots: html-parse-stringify@4.0.1: {} - https-proxy-agent@7.0.6(supports-color@10.2.2): - dependencies: - agent-base: 7.1.4 - debug: 4.4.3(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color - i18next@26.3.6(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 - index-to-position@1.2.0: {} - isbot@5.2.1: {} jiti@2.7.0: {} - js-levenshtein@1.1.6: {} - js-tokens@4.0.0: {} - js-yaml@4.2.0: - dependencies: - argparse: 2.0.1 - jsesc@3.1.0: {} - json-schema-traverse@1.0.0: {} - json5@2.2.3: {} lightningcss-android-arm64@1.32.0: @@ -2575,26 +2417,12 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - minimatch@5.1.9: - dependencies: - brace-expansion: 2.1.2 - ms@2.1.3: {} nanoid@3.3.16: {} node-releases@2.0.51: {} - openapi-typescript@7.13.0(typescript@6.0.3): - dependencies: - '@redocly/openapi-core': 1.34.17(supports-color@10.2.2) - ansi-colors: 4.1.3 - change-case: 5.4.4 - parse-json: 8.3.0 - supports-color: 10.2.2 - typescript: 6.0.3 - yargs-parser: 21.1.1 - oxlint@1.75.0: optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.75.0 @@ -2617,20 +2445,12 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.75.0 '@oxlint/binding-win32-x64-msvc': 1.75.0 - parse-json@8.3.0: - dependencies: - '@babel/code-frame': 7.29.7 - index-to-position: 1.2.0 - type-fest: 4.41.0 - pathe@2.0.3: {} picocolors@1.1.1: {} picomatch@4.0.5: {} - pluralize@8.0.0: {} - postcss@8.5.22: dependencies: nanoid: 3.3.16 @@ -2690,8 +2510,6 @@ snapshots: readdirp@5.0.0: {} - require-from-string@2.0.2: {} - rolldown@1.1.5: dependencies: '@oxc-project/types': 0.139.0 @@ -2725,8 +2543,6 @@ snapshots: source-map-js@1.2.1: {} - supports-color@10.2.2: {} - tailwind-merge@3.6.0: {} tailwindcss@4.3.3: {} @@ -2740,8 +2556,6 @@ snapshots: tslib@2.8.1: {} - type-fest@4.41.0: {} - typescript@6.0.3: {} undici-types@7.18.2: {} @@ -2761,8 +2575,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - uri-js-replace@1.0.1: {} - use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.8): dependencies: react: 19.2.8 @@ -2798,10 +2610,6 @@ snapshots: yallist@3.1.1: {} - yaml-ast-parser@0.0.43: {} - - yargs-parser@21.1.1: {} - zod@4.4.3: {} zustand@5.0.14(@types/react@19.2.17)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)): diff --git a/frontend/src/features/auth/api.ts b/frontend/src/features/auth/api.ts index eb847b9..ef63c10 100644 --- a/frontend/src/features/auth/api.ts +++ b/frontend/src/features/auth/api.ts @@ -1,5 +1,5 @@ import { apiRequest, setAccessToken } from '@/shared/api/client' -import type { AuthResponse, CurrentUser, RegistrationStatus } from '@/shared/api/types' +import type { AuthResponse, RegistrationStatus } from '@/shared/api/types' import { useAuthStore } from './store' /** Публично: включена ли открытая регистрация (для страниц входа/регистрации). */ @@ -19,10 +19,6 @@ export function logout() { return apiRequest('/auth/logout', { method: 'POST' }) } -export function fetchCurrentUser() { - return apiRequest('/auth/me') -} - export function changePassword(currentPassword: string, newPassword: string) { return apiRequest('/auth/change-password', { method: 'POST', body: { currentPassword, newPassword } }) } diff --git a/frontend/src/shared/api/types.ts b/frontend/src/shared/api/types.ts index 8f7dcb8..a5e3886 100644 --- a/frontend/src/shared/api/types.ts +++ b/frontend/src/shared/api/types.ts @@ -53,11 +53,8 @@ export type MediaAssetDto = { source: MediaSource status: MediaAssetStatus durationSeconds: number | null - segmentCount: number | null width: number | null height: number | null - videoCodec: string | null - audioCodec: string | null errorMessage: string | null processingSeconds: number | null createdAt: string @@ -119,7 +116,6 @@ export type EpisodeDto = { title: string | null overview: string | null stillImageId: string | null - airDate: string | null } export type ImageCategory = 'Library' | 'ShowPoster' | 'EpisodeStill' | 'BumperBackground' @@ -214,7 +210,6 @@ export type ChannelShowDto = { blockMode: BlockMode blockValue: number isEnabled: boolean - nextEpisodeIndex: number /** Во сколько раз усиливать вес в предпочтительные часы (1 — без буста). */ preferredWeightMultiplier: number preferredHours: HourWindow[] diff --git a/frontend/src/shared/lib/i18n.ts b/frontend/src/shared/lib/i18n.ts index 1d34563..943725a 100644 --- a/frontend/src/shared/lib/i18n.ts +++ b/frontend/src/shared/lib/i18n.ts @@ -6,7 +6,6 @@ const resources = { translation: { appName: 'TeleWave', nav: { - home: 'Главная', dashboard: 'Эфир', admin: 'Админка', settings: 'Настройки', @@ -24,7 +23,6 @@ const resources = { create: 'Создать', loading: 'Загрузка…', error: 'Что-то пошло не так', - confirm: 'Подтвердить', search: 'Поиск', actions: 'Действия', prevPage: 'Предыдущая страница', @@ -57,11 +55,6 @@ const resources = { blocked: 'Аккаунт заблокирован администратором', genericError: 'Не удалось выполнить вход. Попробуйте ещё раз', }, - dashboard: { - welcome: 'На связи, {{userName}}', - placeholder: 'Список каналов появится здесь позже — пока в эфире только тестовая заставка.', - role: 'Роль', - }, air: { now: 'Сейчас', next: 'Далее', @@ -94,8 +87,6 @@ const resources = { system: 'Системная', create: 'Новая роль', rename: 'Переименовать', - cannotModifySystem: 'Системную роль нельзя изменить или удалить', - roleInUse: 'Роль назначена пользователям', }, users: { title: 'Пользователи', @@ -123,14 +114,9 @@ const resources = { upload: 'Загрузить', uploadToShow: 'Загрузить в шоу', toShowTitle: 'Загрузить и добавить в шоу', - toShowSubtitle: 'Файлов выбрано: {{count}}. После загрузки они добавятся сериями в выбранное шоу.', - autoDetectLabel: 'Определять шоу по названию файла', autoDetectHint: 'Каждый файл привяжется к шоу, чьё оригинальное (или отображаемое) название есть в имени релиза, напр. «The.Simpsons.S33E01…» → The Simpsons.', - toShowShow: 'Шоу', - toShowFallback: 'Для нераспознанных', toShowLibrary: 'В библиотеку', - toShowPick: 'Выберите шоу', toShowSeason: 'Сезон (вручную)', toShowAuto: 'авто', toShowRegex: 'Regex серии', @@ -138,12 +124,10 @@ const resources = { toShowHint: 'Сезон и regex — необязательны: обычно номера распознаются сами (см. ниже). Regex: 1 группа = серия, 2 группы = сезон и серия. Пример: ^(\\d+) для «01. Название.mkv».', toShowPreview: 'Что распознаем', - toShowRecognized: 'распознано {{recognized}} из {{total}}', toShowMatched: 'шоу распознано у {{matched}} из {{total}}', applyToAll: 'Задать всем…', toShowUnknown: '—', toShowConfirm: 'Загрузить и добавить', - uploaded: 'Файл загружен, идёт обработка', uploadedCount: 'Загружено файлов: {{count}}', uploadingCount: 'Загрузка {{done}}/{{total}}', cancelAll: 'Отменить все загрузки', @@ -202,8 +186,6 @@ const resources = { loadedSeasons: 'Загружены сезоны', episodes: 'Серии', episode: 'Серия', - addEpisode: 'Добавить серию', - pickAsset: 'Выберите файл', noEpisodes: 'Серий пока нет', filterAssets: 'Фильтр по имени, напр. Family.Guy.S16', selectAll: 'Выбрать все', @@ -251,7 +233,6 @@ const resources = { bumperBg2: 'Фон (цвет 2)', bumperAccent: 'Акцент', bumperText: 'Текст', - bumperOnlyDifferent: 'Только на смене шоу (не внутри марафона)', bumperTemplates: 'Блоки заставок', bumperTemplatesHint: 'Каждый блок — свой звук и оформление. Первый блок дефолтный, его нельзя удалить. Длительность заставки — по длине звука.', @@ -359,15 +340,12 @@ const resources = { }, metadata: { title: 'Метаданные', - pickFromGallery: 'Выбрать из галереи', pickPoster: 'Из галереи', name: 'Название', originalName: 'Оригинальное название (eng)', originalNamePlaceholder: 'Например: Family Guy', originalNameHint: 'По нему ищутся метаданные; на экранах показывается обычное название.', - source: 'Источник', sourceLabel: 'Источник', - searchPlaceholder: 'Название для поиска', searchBtn: 'Искать', nothingFound: 'Ничего не найдено', apply: 'Применить', @@ -375,7 +353,6 @@ const resources = { overview: 'Описание', year: 'Год', clear: 'Очистить', - uploadPoster: 'Загрузить постер', noPoster: 'Нет постера', refreshEpisodes: 'Обновить серии', refreshing: 'Обновляем…', @@ -396,7 +373,6 @@ const resources = { translation: { appName: 'TeleWave', nav: { - home: 'Home', dashboard: 'On Air', admin: 'Admin', settings: 'Settings', @@ -414,7 +390,6 @@ const resources = { create: 'Create', loading: 'Loading…', error: 'Something went wrong', - confirm: 'Confirm', search: 'Search', actions: 'Actions', prevPage: 'Previous page', @@ -447,11 +422,6 @@ const resources = { blocked: 'Account blocked by an administrator', genericError: 'Could not sign in. Please try again', }, - dashboard: { - welcome: 'On air, {{userName}}', - placeholder: 'The channel list will show up here later — for now, enjoy the test card.', - role: 'Role', - }, air: { now: 'Now', next: 'Up next', @@ -484,8 +454,6 @@ const resources = { system: 'System', create: 'New role', rename: 'Rename', - cannotModifySystem: 'A system role cannot be modified or deleted', - roleInUse: 'Role is assigned to users', }, users: { title: 'Users', @@ -513,14 +481,9 @@ const resources = { upload: 'Upload', uploadToShow: 'Upload to show', toShowTitle: 'Upload and add to show', - toShowSubtitle: '{{count}} file(s) selected. After upload they are added as episodes to the chosen show.', - autoDetectLabel: 'Detect show from file name', autoDetectHint: 'Each file is linked to the show whose original (or display) name appears in the release name, e.g. “The.Simpsons.S33E01…” → The Simpsons.', - toShowShow: 'Show', - toShowFallback: 'For unrecognized', toShowLibrary: 'To library', - toShowPick: 'Pick a show', toShowSeason: 'Season (manual)', toShowAuto: 'auto', toShowRegex: 'Episode regex', @@ -528,12 +491,10 @@ const resources = { toShowHint: 'Season and regex are optional: numbers are usually detected automatically (see below). Regex: 1 group = episode, 2 groups = season and episode. Example: ^(\\d+) for “01. Title.mkv”.', toShowPreview: 'What we detect', - toShowRecognized: '{{recognized}} of {{total}} recognized', toShowMatched: 'show detected for {{matched}} of {{total}}', applyToAll: 'Set for all…', toShowUnknown: '—', toShowConfirm: 'Upload and add', - uploaded: 'File uploaded, processing started', uploadedCount: 'Uploaded files: {{count}}', uploadingCount: 'Uploading {{done}}/{{total}}', cancelAll: 'Cancel all uploads', @@ -592,8 +553,6 @@ const resources = { loadedSeasons: 'Loaded seasons', episodes: 'Episodes', episode: 'Episode', - addEpisode: 'Add episode', - pickAsset: 'Pick a file', noEpisodes: 'No episodes yet', filterAssets: 'Filter by name, e.g. Family.Guy.S16', selectAll: 'Select all', @@ -641,7 +600,6 @@ const resources = { bumperBg2: 'Background (color 2)', bumperAccent: 'Accent', bumperText: 'Text', - bumperOnlyDifferent: 'Only on show change (not within a marathon)', bumperTemplates: 'Bumper blocks', bumperTemplatesHint: 'Each block has its own sound and style. The first block is the default and cannot be removed. Bumper length follows the sound length.', @@ -749,15 +707,12 @@ const resources = { }, metadata: { title: 'Metadata', - pickFromGallery: 'Pick from gallery', pickPoster: 'From gallery', name: 'Name', originalName: 'Original name (eng)', originalNamePlaceholder: 'e.g. Family Guy', originalNameHint: 'Metadata is looked up by this; screens still show the regular name.', - source: 'Source', sourceLabel: 'Source', - searchPlaceholder: 'Title to search', searchBtn: 'Search', nothingFound: 'Nothing found', apply: 'Apply', @@ -765,7 +720,6 @@ const resources = { overview: 'Overview', year: 'Year', clear: 'Clear', - uploadPoster: 'Upload poster', noPoster: 'No poster', refreshEpisodes: 'Refresh episodes', refreshing: 'Refreshing…', diff --git a/frontend/src/shared/ui/card.tsx b/frontend/src/shared/ui/card.tsx index dadf726..b72ea58 100644 --- a/frontend/src/shared/ui/card.tsx +++ b/frontend/src/shared/ui/card.tsx @@ -27,8 +27,3 @@ export const CardContent = forwardRef )) CardContent.displayName = 'CardContent' - -export const CardFooter = forwardRef>(({ className, ...props }, ref) => ( -
-)) -CardFooter.displayName = 'CardFooter' diff --git a/frontend/src/shared/ui/dialog.tsx b/frontend/src/shared/ui/dialog.tsx index cb2fc8e..0720b6a 100644 --- a/frontend/src/shared/ui/dialog.tsx +++ b/frontend/src/shared/ui/dialog.tsx @@ -5,7 +5,6 @@ import { cn } from '@/shared/lib/cn' export const Dialog = DialogPrimitive.Root export const DialogTrigger = DialogPrimitive.Trigger -export const DialogClose = DialogPrimitive.Close export const DialogOverlay = forwardRef< ElementRef,