diff --git a/Manager.App/Components/App.razor b/Manager.App/Components/App.razor
index 68d8c96..fe48475 100644
--- a/Manager.App/Components/App.razor
+++ b/Manager.App/Components/App.razor
@@ -2,7 +2,7 @@
- Application
+ YouTube Manager server
@@ -11,7 +11,6 @@
-
diff --git a/Manager.App/Components/Application/System/EventConsole.razor.cs b/Manager.App/Components/Application/System/EventConsole.razor.cs
index dfd01d8..1a85c9d 100644
--- a/Manager.App/Components/Application/System/EventConsole.razor.cs
+++ b/Manager.App/Components/Application/System/EventConsole.razor.cs
@@ -107,7 +107,11 @@ public partial class EventConsole : ComponentBase
_batchLock.Release();
}
- _serviceEvents.AddRange(batch);
+ foreach (var serviceEvent in _batchBuffer.Where(serviceEvent => !_serviceEvents.Contains(serviceEvent)))
+ {
+ _serviceEvents.Add(serviceEvent);
+ }
+
_lastBatchUpdate = DateTime.UtcNow;
if (_virtualize != null)
diff --git a/Manager.App/Components/Pages/Channels.razor b/Manager.App/Components/Pages/Channels.razor
index 80bbd3e..18010dd 100644
--- a/Manager.App/Components/Pages/Channels.razor
+++ b/Manager.App/Components/Pages/Channels.razor
@@ -6,6 +6,8 @@
@inject ILibraryService LibraryService
@inject IDialogService DialogService
@inject IOptions LibraryOptions
+@inject ClientService ClientService
+@inject ISnackbar Snackbar
Channels
diff --git a/Manager.App/Components/Pages/Channels.razor.cs b/Manager.App/Components/Pages/Channels.razor.cs
index 99fcfbf..a66bc62 100644
--- a/Manager.App/Components/Pages/Channels.razor.cs
+++ b/Manager.App/Components/Pages/Channels.razor.cs
@@ -28,21 +28,21 @@ public partial class Channels : ComponentBase
return;
}
- var client = (ClientPrep)result.Data;
- if (client == null)
+ var clientPrep = (ClientPrep)result.Data;
+ if (clientPrep?.YouTubeClient == null)
{
return;
}
- /*var savedResult = await ClientManager.SaveClientAsync(client);
+ var savedResult = await ClientService.SaveClientAsync(clientPrep.YouTubeClient, clientPrep.Channel);
if (!savedResult.IsSuccess)
{
Snackbar.Add($"Failed to store client: {savedResult.Error?.Description ?? "Unknown!"}", Severity.Error);
}
else
{
- Snackbar.Add($"Client {client.External.Channel?.Handle ?? client.Id} saved!", Severity.Success);
- }*/
+ Snackbar.Add($"Client {clientPrep.Channel?.Handle ?? clientPrep.YouTubeClient.Id} saved!", Severity.Success);
+ }
await InvokeAsync(StateHasChanged);
}
diff --git a/Manager.App/Components/Pages/Development.razor b/Manager.App/Components/Pages/Development.razor
index 668ff32..338665e 100644
--- a/Manager.App/Components/Pages/Development.razor
+++ b/Manager.App/Components/Pages/Development.razor
@@ -1,6 +1,6 @@
@page "/Development"
@using Manager.App.Components.Application.Dev
-Development page
+Development page
diff --git a/Manager.App/Components/Pages/Services.razor b/Manager.App/Components/Pages/Services.razor
index 3a3f96f..e9ca48e 100644
--- a/Manager.App/Components/Pages/Services.razor
+++ b/Manager.App/Components/Pages/Services.razor
@@ -5,7 +5,7 @@
@inject BackgroundServiceRegistry ServiceRegistry
-Services
+Services
@@ -39,4 +39,4 @@
+ Elevation="0" Class="mt-3" Style="flex: 1; display: flex; flex-direction: column; min-height: 350px;"/>
diff --git a/Manager.App/Components/Routes.razor b/Manager.App/Components/Routes.razor
index ae94e9e..608c136 100644
--- a/Manager.App/Components/Routes.razor
+++ b/Manager.App/Components/Routes.razor
@@ -3,4 +3,5 @@
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/Manager.App/Services/System/ClientService.cs b/Manager.App/Services/System/ClientService.cs
index 89abdc7..42541be 100644
--- a/Manager.App/Services/System/ClientService.cs
+++ b/Manager.App/Services/System/ClientService.cs
@@ -1,34 +1,32 @@
using System.Net;
using DotBased.Logging;
using DotBased.Monads;
-using Manager.App.Models.Library;
using Manager.Data.Entities.LibraryContext;
using Manager.YouTube;
+using Manager.YouTube.Models.Innertube;
namespace Manager.App.Services.System;
public class ClientService(IServiceScopeFactory scopeFactory, ILogger logger)
- : ExtendedBackgroundService("ClientService", "Managing YouTube clients", logger, TimeSpan.FromMilliseconds(100))
+ : ExtendedBackgroundService(nameof(ClientService), "Managing YouTube clients", logger, TimeSpan.FromMinutes(10))
{
private readonly List _clients = [];
private CancellationToken _cancellationToken;
private ILibraryService? _libraryService;
- protected override async Task InitializeAsync(CancellationToken stoppingToken)
+ protected override Task InitializeAsync(CancellationToken stoppingToken)
{
_cancellationToken = stoppingToken;
stoppingToken.Register(CancellationRequested);
using var scope = scopeFactory.CreateScope();
_libraryService = scope.ServiceProvider.GetRequiredService();
LogEvent("Initializing service...");
- //Pause();
+ return Task.CompletedTask;
}
- protected override async Task ExecuteServiceAsync(CancellationToken stoppingToken)
+ protected override Task ExecuteServiceAsync(CancellationToken stoppingToken)
{
- LogEvent("Sending event...");
- LogEvent("Sending warning event...", LogSeverity.Warning);
- LogEvent("Sending error event...", LogSeverity.Error);
+ return Task.CompletedTask;
}
private void CancellationRequested()
@@ -36,42 +34,54 @@ public class ClientService(IServiceScopeFactory scopeFactory, ILogger> PrepareClient()
+ public async Task SaveClientAsync(YouTubeClient client, Channel? channelInfo = null, CancellationToken cancellationToken = default)
{
-
- return ResultError.Fail("Not implemented!");
- }
+ if (_libraryService == null)
+ {
+ return ResultError.Fail("Library service is not initialized!.");
+ }
- /*public async Task SaveClientAsync(YouTubeClient client, CancellationToken cancellationToken = default)
- {
if (string.IsNullOrWhiteSpace(client.Id))
{
+ LogEvent("Failed to store client no ID!", LogSeverity.Warning);
return ResultError.Fail("Client does not have an ID, cannot save to library database!");
}
- var channelResult = await libraryService.GetChannelByIdAsync(client.Id, cancellationToken);
+ var channelResult = await _libraryService.GetChannelByIdAsync(client.Id, cancellationToken);
ChannelEntity? channel;
- if (channelResult.IsSuccess)
+ try
{
- channel = channelResult.Value;
- UpdateChannelEntity(channel, client);
+ if (channelResult.IsSuccess)
+ {
+ channel = channelResult.Value;
+ UpdateChannelEntity(client, channel, channelInfo);
+ }
+ else
+ {
+ channel = CreateNewChannelFromClient(client, channelInfo);
+ }
}
- else
+ catch (Exception e)
{
- channel = CreateNewChannelFromClient(client);
+ LogEvent("Failed to save client: " + e.Message, LogSeverity.Warning);
+ return ResultError.Error(e);
}
- var saveResult = await libraryService.SaveChannelAsync(channel, cancellationToken);
+ var saveResult = await _libraryService.SaveChannelAsync(channel, cancellationToken);
return saveResult;
- }*/
+ }
- /*private void UpdateChannelEntity(ChannelEntity channel, YouTubeClient client)
+ private void UpdateChannelEntity(YouTubeClient client, ChannelEntity entity, Channel? channelInfo)
{
- channel.Name = client.External.Channel?.ChannelName;
- channel.Handle = client.External.Channel?.Handle;
- channel.Description = client.External.Channel?.Description;
- var clientAcc = channel.ClientAccount;
+ if (channelInfo != null)
+ {
+ entity.Name = channelInfo.ChannelName;
+ entity.Handle = channelInfo.Handle;
+ entity.Description = channelInfo.Description;
+ }
+
+ var clientAcc = entity.ClientAccount;
if (clientAcc != null)
{
clientAcc.UserAgent = clientAcc.UserAgent;
@@ -99,8 +109,13 @@ public class ClientService(IServiceScopeFactory scopeFactory, ILogger();
foreach (var cookieObj in client.CookieContainer.GetAllCookies())
{
@@ -133,22 +148,21 @@ public class ClientService(IServiceScopeFactory scopeFactory, ILogger> LoadClientByIdAsync(string id)
+ /*public async Task> LoadClientByIdAsync(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return ResultError.Fail("Client ID is empty!");
}
-
return ResultError.Fail("Not implemented");
- }
+ }*/
}
\ No newline at end of file
diff --git a/Manager.Data/Contexts/AuditInterceptor.cs b/Manager.Data/Contexts/AuditInterceptor.cs
new file mode 100644
index 0000000..401beca
--- /dev/null
+++ b/Manager.Data/Contexts/AuditInterceptor.cs
@@ -0,0 +1,111 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Manager.Data.Entities.Audit;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+
+namespace Manager.Data.Contexts;
+
+public class AuditInterceptor : SaveChangesInterceptor
+{
+ public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result)
+ {
+ AddHistory(eventData.Context);
+ return base.SavingChanges(eventData, result);
+ }
+
+ public override ValueTask> SavingChangesAsync(
+ DbContextEventData eventData,
+ InterceptionResult result,
+ CancellationToken cancellationToken = default)
+ {
+ AddHistory(eventData.Context);
+ return base.SavingChangesAsync(eventData, result, cancellationToken);
+ }
+
+ private void AddHistory(DbContext? context)
+ {
+ if (context == null) return;
+
+ var entries = context.ChangeTracker.Entries()
+ .Where(e => e.State is EntityState.Modified or EntityState.Deleted or EntityState.Added && Attribute.IsDefined(e.Entity.GetType(),
+ typeof(AuditableAttribute)));
+
+ var histories = new List();
+
+ foreach (var entry in entries)
+ {
+ var primaryKey = entry.Properties.First(p => p.Metadata.IsPrimaryKey()).CurrentValue?.ToString() ?? "Unknown";
+
+ var declaredProperties = entry.Entity.GetType()
+ .GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
+ .Where(p => !Attribute.IsDefined(p.DeclaringType!, typeof(NoAuditAttribute)))
+ .Select(p => p.Name)
+ .ToHashSet();
+
+ var allowedProperties = entry.Properties.Where(p => declaredProperties.Contains(p.Metadata.Name));
+
+ switch (entry.State)
+ {
+ case EntityState.Added:
+ histories.AddRange(allowedProperties
+ .Where(p => p.CurrentValue != null)
+ .Select(p => CreateHistory(entry, p, entry.State, primaryKey))
+ );
+ break;
+ case EntityState.Modified:
+ histories.AddRange(allowedProperties
+ .Where(p => p.IsModified)
+ .Select(p => CreateHistory(entry, p, entry.State, primaryKey))
+ );
+ break;
+ case EntityState.Deleted:
+ histories.AddRange(allowedProperties
+ .Select(p => CreateHistory(entry, p, entry.State, primaryKey))
+ );
+ break;
+ }
+ }
+
+ if (histories.Count != 0)
+ {
+ context.Set().AddRange(histories);
+ }
+ }
+
+ private EntityHistory CreateHistory(EntityEntry entry, PropertyEntry prop, EntityState changeType, string? primaryKey)
+ {
+ return new EntityHistory
+ {
+ EntityName = entry.Entity.GetType().Name,
+ EntityId = primaryKey ?? "Unknown",
+ PropertyName = prop.Metadata.Name,
+ OldValue = SerializeValue(prop.OriginalValue),
+ NewValue = SerializeValue(prop.CurrentValue),
+ ModifiedUtc = DateTime.UtcNow,
+ ChangedBy = "SYSTEM",
+ ChangeType = changeType
+ };
+ }
+
+ private readonly JsonSerializerOptions _jsonSerializerOptions = new()
+ {
+ WriteIndented = false,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ };
+
+ private string? SerializeValue(object? value)
+ {
+ if (value == null) return null;
+
+ var type = value.GetType();
+
+ if (type.IsPrimitive || type == typeof(string) || type == typeof(DateTime) || type == typeof(decimal))
+ {
+ return value.ToString();
+ }
+
+ return JsonSerializer.Serialize(value, _jsonSerializerOptions);
+ }
+}
\ No newline at end of file
diff --git a/Manager.Data/Contexts/DateInterceptor.cs b/Manager.Data/Contexts/DateInterceptor.cs
new file mode 100644
index 0000000..099ccec
--- /dev/null
+++ b/Manager.Data/Contexts/DateInterceptor.cs
@@ -0,0 +1,38 @@
+using Manager.Data.Entities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+
+namespace Manager.Data.Contexts;
+
+public class DateInterceptor : SaveChangesInterceptor
+{
+ public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result)
+ {
+ UpdateEntryDates(eventData.Context);
+ return base.SavingChanges(eventData, result);
+ }
+
+ public override ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result,
+ CancellationToken cancellationToken = new())
+ {
+ UpdateEntryDates(eventData.Context);
+ return base.SavingChangesAsync(eventData, result, cancellationToken);
+ }
+
+ private void UpdateEntryDates(DbContext? context)
+ {
+ if (context == null) return;
+
+ var entries = context.ChangeTracker.Entries().Where(x => x is { Entity: DateTimeBase, State: EntityState.Added or EntityState.Modified });
+
+ foreach (var entity in entries)
+ {
+ ((DateTimeBase)entity.Entity).LastModifiedUtc = DateTime.UtcNow;
+
+ if (entity.State == EntityState.Added)
+ {
+ ((DateTimeBase)entity.Entity).CreatedAtUtc = DateTime.UtcNow;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Manager.Data/Contexts/LibraryDbContext.cs b/Manager.Data/Contexts/LibraryDbContext.cs
index 51c1b3b..4ff3848 100644
--- a/Manager.Data/Contexts/LibraryDbContext.cs
+++ b/Manager.Data/Contexts/LibraryDbContext.cs
@@ -1,4 +1,4 @@
-using Manager.Data.Entities;
+using Manager.Data.Entities.Audit;
using Manager.Data.Entities.LibraryContext;
using Manager.Data.Entities.LibraryContext.Join;
using Microsoft.EntityFrameworkCore;
@@ -13,6 +13,8 @@ public sealed class LibraryDbContext : DbContext
ChangeTracker.LazyLoadingEnabled = false;
Database.EnsureCreated();
}
+
+ public DbSet Histories { get; set; }
public DbSet Captions { get; set; }
public DbSet Channels { get; set; }
@@ -21,9 +23,20 @@ public sealed class LibraryDbContext : DbContext
public DbSet Media { get; set; }
public DbSet MediaFormats { get; set; }
public DbSet Playlists { get; set; }
+ // Other media (images)?
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.AddInterceptors(new DateInterceptor(), new AuditInterceptor());
+ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+ modelBuilder.Entity(eh =>
+ {
+ eh.ToTable("entity_history");
+ });
+
modelBuilder.Entity(ce =>
{
ce.ToTable("captions");
@@ -101,31 +114,4 @@ public sealed class LibraryDbContext : DbContext
base.OnModelCreating(modelBuilder);
}
-
- public override int SaveChanges()
- {
- UpdateEntryDates();
- return base.SaveChanges();
- }
-
- public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new())
- {
- UpdateEntryDates();
- return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
- }
-
- private void UpdateEntryDates()
- {
- var entries = ChangeTracker.Entries().Where(x => x is { Entity: DateTimeBase, State: EntityState.Added or EntityState.Modified });
-
- foreach (var entity in entries)
- {
- ((DateTimeBase)entity.Entity).LastModifiedUtc = DateTime.UtcNow;
-
- if (entity.State == EntityState.Added)
- {
- ((DateTimeBase)entity.Entity).CreatedAtUtc = DateTime.UtcNow;
- }
- }
- }
}
\ No newline at end of file
diff --git a/Manager.Data/Entities/Audit/AuditableAttribute.cs b/Manager.Data/Entities/Audit/AuditableAttribute.cs
new file mode 100644
index 0000000..df7c5ee
--- /dev/null
+++ b/Manager.Data/Entities/Audit/AuditableAttribute.cs
@@ -0,0 +1,9 @@
+namespace Manager.Data.Entities.Audit;
+
+///
+/// Make all properties in the entity audible, if they are changed this will be stored as a history in the db.
+///
+[AttributeUsage(AttributeTargets.Class)]
+public class AuditableAttribute : Attribute
+{
+}
\ No newline at end of file
diff --git a/Manager.Data/Entities/Audit/EntityHistory.cs b/Manager.Data/Entities/Audit/EntityHistory.cs
new file mode 100644
index 0000000..a824e78
--- /dev/null
+++ b/Manager.Data/Entities/Audit/EntityHistory.cs
@@ -0,0 +1,22 @@
+using System.ComponentModel.DataAnnotations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Manager.Data.Entities.Audit;
+
+public class EntityHistory
+{
+ [MaxLength(200)]
+ public required string EntityName { get; set; }
+ [MaxLength(200)]
+ public required string EntityId { get; set; }
+ [MaxLength(200)]
+ public required string PropertyName { get; set; }
+ [MaxLength(1000)]
+ public string? OldValue { get; set; }
+ [MaxLength(1000)]
+ public string? NewValue { get; set; }
+ public DateTime ModifiedUtc { get; set; } = DateTime.UtcNow;
+ [MaxLength(200)]
+ public string? ChangedBy { get; set; }
+ public EntityState ChangeType { get; set; }
+}
\ No newline at end of file
diff --git a/Manager.Data/Entities/Audit/NoAuditAttribute.cs b/Manager.Data/Entities/Audit/NoAuditAttribute.cs
new file mode 100644
index 0000000..98d948f
--- /dev/null
+++ b/Manager.Data/Entities/Audit/NoAuditAttribute.cs
@@ -0,0 +1,9 @@
+namespace Manager.Data.Entities.Audit;
+
+///
+/// Specifies to ignore the properties in the entity to not audit.
+///
+[AttributeUsage(AttributeTargets.Class)]
+public class NoAuditAttribute : Attribute
+{
+}
diff --git a/Manager.Data/Entities/DateTimeBase.cs b/Manager.Data/Entities/DateTimeBase.cs
index 3638ed8..cc09714 100644
--- a/Manager.Data/Entities/DateTimeBase.cs
+++ b/Manager.Data/Entities/DateTimeBase.cs
@@ -1,5 +1,8 @@
+using Manager.Data.Entities.Audit;
+
namespace Manager.Data.Entities;
+[NoAudit]
public abstract class DateTimeBase
{
public DateTime CreatedAtUtc { get; set; } = DateTime.UtcNow;
diff --git a/Manager.Data/Entities/LibraryContext/CaptionEntity.cs b/Manager.Data/Entities/LibraryContext/CaptionEntity.cs
index afaea8d..2fd72be 100644
--- a/Manager.Data/Entities/LibraryContext/CaptionEntity.cs
+++ b/Manager.Data/Entities/LibraryContext/CaptionEntity.cs
@@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations;
+using Manager.Data.Entities.Audit;
namespace Manager.Data.Entities.LibraryContext;
+[Auditable]
public class CaptionEntity : DateTimeBase
{
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
diff --git a/Manager.Data/Entities/LibraryContext/ChannelEntity.cs b/Manager.Data/Entities/LibraryContext/ChannelEntity.cs
index 555358e..bf68f9b 100644
--- a/Manager.Data/Entities/LibraryContext/ChannelEntity.cs
+++ b/Manager.Data/Entities/LibraryContext/ChannelEntity.cs
@@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations;
+using Manager.Data.Entities.Audit;
namespace Manager.Data.Entities.LibraryContext;
+[Auditable]
public class ChannelEntity : DateTimeBase
{
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
diff --git a/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs b/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs
index 154c4d0..10c9ae4 100644
--- a/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs
+++ b/Manager.Data/Entities/LibraryContext/ClientAccountEntity.cs
@@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations;
+using Manager.Data.Entities.Audit;
namespace Manager.Data.Entities.LibraryContext;
+[Auditable]
public class ClientAccountEntity : DateTimeBase
{
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
diff --git a/Manager.Data/Entities/LibraryContext/HttpCookieEntity.cs b/Manager.Data/Entities/LibraryContext/HttpCookieEntity.cs
index c995e4c..0b76d80 100644
--- a/Manager.Data/Entities/LibraryContext/HttpCookieEntity.cs
+++ b/Manager.Data/Entities/LibraryContext/HttpCookieEntity.cs
@@ -1,9 +1,13 @@
using System.ComponentModel.DataAnnotations;
+using Manager.Data.Entities.Audit;
namespace Manager.Data.Entities.LibraryContext;
+[Auditable]
public class HttpCookieEntity : DateTimeBase
{
+ [MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
+ public required string ClientId { get; set; }
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
public required string Name { get; set; }
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
@@ -15,6 +19,4 @@ public class HttpCookieEntity : DateTimeBase
public DateTimeOffset? ExpiresUtc { get; set; }
public bool Secure { get; set; }
public bool HttpOnly { get; set; }
- [MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
- public required string ClientId { get; set; }
}
diff --git a/Manager.Data/Entities/LibraryContext/MediaEntity.cs b/Manager.Data/Entities/LibraryContext/MediaEntity.cs
index 7f6f13f..de43600 100644
--- a/Manager.Data/Entities/LibraryContext/MediaEntity.cs
+++ b/Manager.Data/Entities/LibraryContext/MediaEntity.cs
@@ -1,8 +1,10 @@
using System.ComponentModel.DataAnnotations;
+using Manager.Data.Entities.Audit;
using Manager.Data.Entities.LibraryContext.Join;
namespace Manager.Data.Entities.LibraryContext;
+[Auditable]
public class MediaEntity : DateTimeBase
{
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
@@ -17,7 +19,6 @@ public class MediaEntity : DateTimeBase
public List Formats { get; set; } = [];
public List Captions { get; set; } = [];
public List PlaylistMedias { get; set; } = [];
-
public MediaExternalState ExternalState { get; set; } = MediaExternalState.Online;
public bool IsDownloaded { get; set; }
public MediaState State { get; set; } = MediaState.Indexed;
diff --git a/Manager.Data/Entities/LibraryContext/PlaylistEntity.cs b/Manager.Data/Entities/LibraryContext/PlaylistEntity.cs
index 8f09725..580ed46 100644
--- a/Manager.Data/Entities/LibraryContext/PlaylistEntity.cs
+++ b/Manager.Data/Entities/LibraryContext/PlaylistEntity.cs
@@ -1,8 +1,10 @@
using System.ComponentModel.DataAnnotations;
+using Manager.Data.Entities.Audit;
using Manager.Data.Entities.LibraryContext.Join;
namespace Manager.Data.Entities.LibraryContext;
+[Auditable]
public class PlaylistEntity : DateTimeBase
{
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
diff --git a/Manager.YouTube/NetworkService.cs b/Manager.YouTube/NetworkService.cs
index a37b3e2..4eff9eb 100644
--- a/Manager.YouTube/NetworkService.cs
+++ b/Manager.YouTube/NetworkService.cs
@@ -31,4 +31,9 @@ public static class NetworkService
return ResultError.Error(e);
}
}
+
+ public static async Task> DownloadBytesAsync(HttpRequestMessage request, YouTubeClient client)
+ {
+ return ResultError.Fail("Not implemented");
+ }
}
\ No newline at end of file
diff --git a/Manager.YouTube/YouTubeClient.cs b/Manager.YouTube/YouTubeClient.cs
index fe29612..daaf66b 100644
--- a/Manager.YouTube/YouTubeClient.cs
+++ b/Manager.YouTube/YouTubeClient.cs
@@ -204,7 +204,7 @@ public sealed class YouTubeClient : IDisposable
public void Dispose()
{
- HttpClient?.Dispose();
+ HttpClient.Dispose();
}
private async Task> GetCurrentAccountIdAsync()