diff --git a/Manager.App/Components/Layout/NavMenu.razor b/Manager.App/Components/Layout/NavMenu.razor index da7f50a..dd3ae86 100644 --- a/Manager.App/Components/Layout/NavMenu.razor +++ b/Manager.App/Components/Layout/NavMenu.razor @@ -1,9 +1,14 @@  Home - Channels - Library - Playlists - Development - Services + + Accounts + Channels + Playlists + Info + + + Development + Services + \ No newline at end of file diff --git a/Manager.App/Components/Pages/Accounts.razor b/Manager.App/Components/Pages/Accounts.razor new file mode 100644 index 0000000..f693a25 --- /dev/null +++ b/Manager.App/Components/Pages/Accounts.razor @@ -0,0 +1,48 @@ +@page "/Accounts" +@using Manager.App.Models.Settings +@using Manager.App.Services.System +@using Microsoft.Extensions.Options + +@inject ILibraryService LibraryService +@inject IDialogService DialogService +@inject IOptions LibraryOptions +@inject ClientService ClientService +@inject ISnackbar Snackbar + +Accounts + + + + + Add account + + + + + + Accounts + + + + + Name + Handle + ID + + + @context.Channel?.Name + @context.Channel?.Handle + @context.Id + + + No channels found + + + Loading... + + + + + + \ No newline at end of file diff --git a/Manager.App/Components/Pages/Accounts.razor.cs b/Manager.App/Components/Pages/Accounts.razor.cs new file mode 100644 index 0000000..f46ab89 --- /dev/null +++ b/Manager.App/Components/Pages/Accounts.razor.cs @@ -0,0 +1,74 @@ +using Manager.App.Components.Dialogs; +using Manager.App.Models.Library; +using Manager.Data.Entities.LibraryContext; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace Manager.App.Components.Pages; + +public partial class Accounts : ComponentBase +{ + private MudTable? _table; + private readonly DialogOptions _dialogOptions = new() { BackdropClick = false, CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.ExtraLarge }; + private string _search = ""; + + private async Task> ServerReload(TableState state, CancellationToken token) + { + var results = await LibraryService.GetAccountsAsync(_search, state.Page * state.PageSize, state.PageSize, token); + return !results.IsSuccess ? new TableData() : new TableData() { Items = results.Value, TotalItems = results.Total }; + } + + private void OnSearch(string text) + { + _search = text; + _table?.ReloadServerData(); + } + + private async Task OnAddAccountDialogAsync() + { + var libSettings = LibraryOptions.Value; + var parameters = new DialogParameters { { x => x.DefaultUserAgent, libSettings.DefaultUserAgent } }; + var dialog = await DialogService.ShowAsync("Add account", parameters, _dialogOptions); + var result = await dialog.Result; + + if (result == null || result.Canceled || result.Data == null) + { + return; + } + + var clientChannel = (ClientChannel)result.Data; + if (clientChannel?.YouTubeClient == null) + { + Snackbar.Add("No YouTube client received.", Severity.Error); + return; + } + + var savedClientResult = await ClientService.SaveClientAsync(clientChannel.YouTubeClient); + if (savedClientResult.IsSuccess) + { + if (_table != null) + { + await _table.ReloadServerData(); + } + Snackbar.Add($"Client {clientChannel.Channel?.Handle ?? clientChannel.YouTubeClient.Id} saved!", Severity.Success); + ClientService.AddClient(clientChannel.YouTubeClient); + } + else + { + Snackbar.Add($"Failed to store client: {savedClientResult.Error?.Description ?? "Unknown!"}", Severity.Error); + } + + if (clientChannel.Channel == null) + { + Snackbar.Add("No channel information received!", Severity.Warning); + } + else + { + var saveChannelResult = await LibraryService.SaveChannelAsync(clientChannel.Channel); + if (!saveChannelResult.IsSuccess) + { + Snackbar.Add("Failed to save channel information", Severity.Warning); + } + } + } +} \ No newline at end of file diff --git a/Manager.App/Components/Pages/Channels.razor b/Manager.App/Components/Pages/Channels.razor index b8887a4..38d7ab9 100644 --- a/Manager.App/Components/Pages/Channels.razor +++ b/Manager.App/Components/Pages/Channels.razor @@ -1,37 +1,26 @@ @page "/Channels" -@using Manager.App.Models.Settings -@using Manager.App.Services.System -@using Microsoft.Extensions.Options @inject ILibraryService LibraryService -@inject IDialogService DialogService -@inject IOptions LibraryOptions -@inject ClientService ClientService -@inject ISnackbar Snackbar Channels - - - - Add account - - - - Channels + Channels stored in the library + + Name + Handle Channel id - Has login - @context.Channel?.Name + @context.Name + @context.Handle @context.Id - @(context.HttpCookies.Any()) No channels found diff --git a/Manager.App/Components/Pages/Channels.razor.cs b/Manager.App/Components/Pages/Channels.razor.cs index 6c526ba..bc47493 100644 --- a/Manager.App/Components/Pages/Channels.razor.cs +++ b/Manager.App/Components/Pages/Channels.razor.cs @@ -1,5 +1,3 @@ -using Manager.App.Components.Dialogs; -using Manager.App.Models.Library; using Manager.Data.Entities.LibraryContext; using Microsoft.AspNetCore.Components; using MudBlazor; @@ -8,60 +6,18 @@ namespace Manager.App.Components.Pages; public partial class Channels : ComponentBase { - private readonly DialogOptions _dialogOptions = new() { BackdropClick = false, CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.ExtraLarge }; - private MudTable? _table; + private MudTable? _table; + private string _search = ""; - private async Task> ServerReload(TableState state, CancellationToken token) + private async Task> ServerReload(TableState state, CancellationToken token) { - var results = await LibraryService.GetAccountsAsync(null, state.Page * state.PageSize, state.PageSize, token); - return !results.IsSuccess ? new TableData() : new TableData { Items = results.Value, TotalItems = results.Total }; + var results = await LibraryService.GetChannelsAsync(_search, state.Page * state.PageSize, state.PageSize, token); + return !results.IsSuccess ? new TableData() : new TableData { Items = results.Value, TotalItems = results.Total }; } - private async Task OnAddAccountDialogAsync() + private void OnSearch(string text) { - var libSettings = LibraryOptions.Value; - var parameters = new DialogParameters { { x => x.DefaultUserAgent, libSettings.DefaultUserAgent } }; - var dialog = await DialogService.ShowAsync("Add account", parameters, _dialogOptions); - var result = await dialog.Result; - - if (result == null || result.Canceled || result.Data == null) - { - return; - } - - var clientChannel = (ClientChannel)result.Data; - if (clientChannel?.YouTubeClient == null) - { - Snackbar.Add("No YouTube client received.", Severity.Error); - return; - } - - var savedClientResult = await ClientService.SaveClientAsync(clientChannel.YouTubeClient); - if (savedClientResult.IsSuccess) - { - if (_table != null) - { - await _table.ReloadServerData(); - } - Snackbar.Add($"Client {clientChannel.Channel?.Handle ?? clientChannel.YouTubeClient.Id} saved!", Severity.Success); - ClientService.AddClient(clientChannel.YouTubeClient); - } - else - { - Snackbar.Add($"Failed to store client: {savedClientResult.Error?.Description ?? "Unknown!"}", Severity.Error); - } - - if (clientChannel.Channel == null) - { - Snackbar.Add("No channel information received!", Severity.Warning); - } - else - { - var saveChannelResult = await LibraryService.SaveChannelAsync(clientChannel.Channel); - if (!saveChannelResult.IsSuccess) - { - Snackbar.Add("Failed to save channel information", Severity.Warning); - } - } + _search = text; + _table?.ReloadServerData(); } } \ No newline at end of file diff --git a/Manager.App/Services/ILibraryService.cs b/Manager.App/Services/ILibraryService.cs index 87ee3b1..e3709b5 100644 --- a/Manager.App/Services/ILibraryService.cs +++ b/Manager.App/Services/ILibraryService.cs @@ -14,5 +14,5 @@ public interface ILibraryService public Task SaveChannelAsync(InnertubeChannel innertubeChannel, CancellationToken cancellationToken = default); public Task> GetLibraryInfoAsync(CancellationToken cancellationToken = default); public Task> GetAccountsAsync(string? search, int offset = 0, int total = 20, CancellationToken cancellationToken = default); - public Task> GetChannelsAsync(int offset = 0, int total = 20, CancellationToken cancellationToken = default); + public Task> GetChannelsAsync(string? search, int offset = 0, int total = 20, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Manager.App/Services/LibraryService.cs b/Manager.App/Services/LibraryService.cs index c3e3643..e5891db 100644 --- a/Manager.App/Services/LibraryService.cs +++ b/Manager.App/Services/LibraryService.cs @@ -151,6 +151,7 @@ public class LibraryService : ILibraryService var channel = await context.Channels .Include(c => c.ClientAccount) .ThenInclude(p => p!.HttpCookies) + .Include(f => f.Files) .FirstOrDefaultAsync(c => c.Id == id, cancellationToken); if (channel == null) @@ -288,13 +289,33 @@ public class LibraryService : ILibraryService } } - public async Task> GetChannelsAsync(int offset = 0, int total = 20, CancellationToken cancellationToken = default) + public async Task> GetChannelsAsync(string? search, int offset = 0, int total = 20, CancellationToken cancellationToken = default) { try { await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken); - var orderedAccounts = context.Channels.Include(x => x.ClientAccount).OrderBy(x => x.Id); - return new ListResultReturn(orderedAccounts.Skip(offset).Take(total).ToList(),orderedAccounts.Count()); + var orderedAccounts = context.Channels.OrderBy(x => x.Id); + + var totalChannels = orderedAccounts.Count(); + if (!string.IsNullOrWhiteSpace(search) && orderedAccounts.Any()) + { + var normalizedSearch = $"%{search.ToLower()}%"; + var searched = orderedAccounts + .Where(ca => + EF.Functions.Like( + ( + ca.Id.ToString() + " " + + ca.Name + " " + + ca.Handle + ).ToLower(), + normalizedSearch + ) + ); + totalChannels = searched.Count(); + orderedAccounts = searched.OrderByDescending(ca => ca.Id); + } + + return new ListResultReturn(totalChannels == 0 ? [] : orderedAccounts.Skip(offset).Take(total).ToList(), totalChannels); } catch (Exception e) { diff --git a/Manager.Data/Contexts/LibraryDbContext.cs b/Manager.Data/Contexts/LibraryDbContext.cs index 69256d5..c497328 100644 --- a/Manager.Data/Contexts/LibraryDbContext.cs +++ b/Manager.Data/Contexts/LibraryDbContext.cs @@ -58,6 +58,9 @@ public sealed class LibraryDbContext : DbContext .WithOne(x => x.Channel) .HasForeignKey(e => e.Id) .IsRequired(false); + channel.HasMany(x => x.Files) + .WithOne() + .HasForeignKey(f => f.ForeignKey); }); modelBuilder.Entity(cae => @@ -71,6 +74,9 @@ public sealed class LibraryDbContext : DbContext .WithOne(ca => ca.ClientAccount) .HasForeignKey(ce => ce.Id) .IsRequired(false); + cae.HasMany(x => x.Files) + .WithOne() + .HasForeignKey(f => f.ForeignKey); }); modelBuilder.Entity(httpce => diff --git a/Manager.Data/Entities/LibraryContext/ChannelEntity.cs b/Manager.Data/Entities/LibraryContext/ChannelEntity.cs index bf68f9b..209154f 100644 --- a/Manager.Data/Entities/LibraryContext/ChannelEntity.cs +++ b/Manager.Data/Entities/LibraryContext/ChannelEntity.cs @@ -17,4 +17,5 @@ public class ChannelEntity : DateTimeBase public List Media { get; set; } = []; public List Playlists { get; set; } = []; public ClientAccountEntity? ClientAccount { get; set; } + public List? Files { get; set; } } \ No newline at end of file diff --git a/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs b/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs index 8c30f15..808d86c 100644 --- a/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs +++ b/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs @@ -12,4 +12,5 @@ public class ClientAccountEntity : DateTimeBase [MaxLength(DataConstants.DbContext.DefaultDbStringSize)] public string? UserAgent { get; set; } public ChannelEntity? Channel { get; set; } + public List? Files { get; set; } } \ No newline at end of file