Cleanup ef repositories

This commit is contained in:
Zoe Roux 2024-04-19 21:03:29 +02:00
parent 69e8340c95
commit 1d553daeaf
No known key found for this signature in database
10 changed files with 87 additions and 425 deletions

View File

@ -31,46 +31,20 @@ namespace Kyoo.Core.Controllers;
/// <summary> /// <summary>
/// A local repository to handle collections /// A local repository to handle collections
/// </summary> /// </summary>
public class CollectionRepository : LocalRepository<Collection> public class CollectionRepository(DatabaseContext database) : LocalRepository<Collection>(database)
{ {
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
/// Create a new <see cref="CollectionRepository"/>.
/// </summary>
/// <param name="database">The database handle to use</param>
/// <param name="thumbs">The thumbnail manager used to store images.</param>
public CollectionRepository(DatabaseContext database, IThumbnailsManager thumbs)
: base(database, thumbs)
{
_database = database;
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Collection>> Search( public override async Task<ICollection<Collection>> Search(
string query, string query,
Include<Collection>? include = default Include<Collection>? include = default
) )
{ {
return await AddIncludes(_database.Collections, include) return await AddIncludes(Database.Collections, include)
.Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
/// <inheritdoc />
public override async Task<Collection> Create(Collection obj)
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
await IRepository<Collection>.OnResourceCreated(obj);
return obj;
}
/// <inheritdoc /> /// <inheritdoc />
protected override async Task Validate(Collection resource) protected override async Task Validate(Collection resource)
{ {
@ -82,21 +56,13 @@ public class CollectionRepository : LocalRepository<Collection>
public async Task AddMovie(Guid id, Guid movieId) public async Task AddMovie(Guid id, Guid movieId)
{ {
_database.AddLinks<Collection, Movie>(id, movieId); Database.AddLinks<Collection, Movie>(id, movieId);
await _database.SaveChangesAsync(); await Database.SaveChangesAsync();
} }
public async Task AddShow(Guid id, Guid showId) public async Task AddShow(Guid id, Guid showId)
{ {
_database.AddLinks<Collection, Show>(id, showId); Database.AddLinks<Collection, Show>(id, showId);
await _database.SaveChangesAsync(); await Database.SaveChangesAsync();
}
/// <inheritdoc />
public override async Task Delete(Collection obj)
{
_database.Entry(obj).State = EntityState.Deleted;
await _database.SaveChangesAsync();
await base.Delete(obj);
} }
} }

View File

@ -32,11 +32,8 @@ namespace Kyoo.Core.Controllers;
/// <summary> /// <summary>
/// A local repository to handle episodes. /// A local repository to handle episodes.
/// </summary> /// </summary>
public class EpisodeRepository( public class EpisodeRepository(DatabaseContext database, IRepository<Show> shows)
DatabaseContext database, : LocalRepository<Episode>(database)
IRepository<Show> shows,
IThumbnailsManager thumbs
) : LocalRepository<Episode>(database, thumbs)
{ {
static EpisodeRepository() static EpisodeRepository()
{ {
@ -64,34 +61,18 @@ public class EpisodeRepository(
Include<Episode>? include = default Include<Episode>? include = default
) )
{ {
return await AddIncludes(database.Episodes, include) return await AddIncludes(Database.Episodes, include)
.Where(x => EF.Functions.ILike(x.Name!, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name!, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
protected override Task<Episode?> GetDuplicated(Episode item)
{
if (item is { SeasonNumber: not null, EpisodeNumber: not null })
return database.Episodes.FirstOrDefaultAsync(x =>
x.ShowId == item.ShowId
&& x.SeasonNumber == item.SeasonNumber
&& x.EpisodeNumber == item.EpisodeNumber
);
return database.Episodes.FirstOrDefaultAsync(x =>
x.ShowId == item.ShowId && x.AbsoluteNumber == item.AbsoluteNumber
);
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<Episode> Create(Episode obj) public override async Task<Episode> Create(Episode obj)
{ {
// Set it for the OnResourceCreated event and the return value.
obj.ShowSlug = obj.Show?.Slug ?? (await shows.Get(obj.ShowId)).Slug; obj.ShowSlug = obj.Show?.Slug ?? (await shows.Get(obj.ShowId)).Slug;
await base.Create(obj); return await base.Create(obj);
database.Entry(obj).State = EntityState.Added;
await database.SaveChangesAsync(() => GetDuplicated(obj));
await IRepository<Episode>.OnResourceCreated(obj);
return obj;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -111,7 +92,7 @@ public class EpisodeRepository(
} }
if (resource.SeasonId == null && resource.SeasonNumber != null) if (resource.SeasonId == null && resource.SeasonNumber != null)
{ {
resource.Season = await database.Seasons.FirstOrDefaultAsync(x => resource.Season = await Database.Seasons.FirstOrDefaultAsync(x =>
x.ShowId == resource.ShowId && x.SeasonNumber == resource.SeasonNumber x.ShowId == resource.ShowId && x.SeasonNumber == resource.SeasonNumber
); );
} }
@ -120,14 +101,40 @@ public class EpisodeRepository(
/// <inheritdoc /> /// <inheritdoc />
public override async Task Delete(Episode obj) public override async Task Delete(Episode obj)
{ {
int epCount = await database int epCount = await Database
.Episodes.Where(x => x.ShowId == obj.ShowId) .Episodes.Where(x => x.ShowId == obj.ShowId)
.Take(2) .Take(2)
.CountAsync(); .CountAsync();
database.Entry(obj).State = EntityState.Deleted;
await database.SaveChangesAsync();
await base.Delete(obj);
if (epCount == 1) if (epCount == 1)
await shows.Delete(obj.ShowId); await shows.Delete(obj.ShowId);
else
await base.Delete(obj);
}
/// <inheritdoc/>
public override async Task DeleteAll(Filter<Episode> filter)
{
ICollection<Episode> items = await GetAll(filter);
Guid[] ids = items.Select(x => x.Id).ToArray();
await Database.Set<Episode>().Where(x => ids.Contains(x.Id)).ExecuteDeleteAsync();
foreach (Episode resource in items)
await IRepository<Episode>.OnResourceDeleted(resource);
Guid[] showIds = await Database
.Set<Episode>()
.Where(filter.ToEfLambda())
.Select(x => x.Show!)
.Where(x => !x.Episodes!.Any())
.Select(x => x.Id)
.ToArrayAsync();
if (!showIds.Any())
return;
Filter<Show>[] showFilters = showIds
.Select(x => new Filter<Show>.Eq(nameof(Show.Id), x))
.ToArray();
await shows.DeleteAll(Filter.Or(showFilters)!);
} }
} }

View File

@ -29,38 +29,14 @@ using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Utils; using Kyoo.Abstractions.Models.Utils;
using Kyoo.Postgresql; using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers; namespace Kyoo.Core.Controllers;
/// <summary> public abstract class LocalRepository<T>(DatabaseContext database) : IRepository<T>
/// A base class to create repositories using Entity Framework.
/// </summary>
/// <typeparam name="T">The type of this repository</typeparam>
public abstract class LocalRepository<T> : IRepository<T>
where T : class, IResource, IQuery where T : class, IResource, IQuery
{ {
/// <summary> public DatabaseContext Database => database;
/// The Entity Framework's Database handle.
/// </summary>
protected DbContext Database { get; }
/// <summary>
/// The thumbnail manager used to store images.
/// </summary>
private readonly IThumbnailsManager _thumbs;
/// <summary>
/// Create a new base <see cref="LocalRepository{T}"/> with the given database handle.
/// </summary>
/// <param name="database">A database connection to load resources of type <typeparamref name="T"/></param>
/// <param name="thumbs">The thumbnail manager used to store images.</param>
protected LocalRepository(DbContext database, IThumbnailsManager thumbs)
{
Database = database;
_thumbs = thumbs;
}
/// <inheritdoc/> /// <inheritdoc/>
public Type RepositoryType => typeof(T); public Type RepositoryType => typeof(T);
@ -127,12 +103,6 @@ public abstract class LocalRepository<T> : IRepository<T>
return query; return query;
} }
/// <summary>
/// Get a resource from it's ID and make the <see cref="Database"/> instance track it.
/// </summary>
/// <param name="id">The ID of the resource</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The tracked resource with the given ID</returns>
protected virtual async Task<T> GetWithTracking(Guid id) protected virtual async Task<T> GetWithTracking(Guid id)
{ {
T? ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.Id == id); T? ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.Id == id);
@ -174,11 +144,6 @@ public abstract class LocalRepository<T> : IRepository<T>
return ret; return ret;
} }
protected virtual Task<T?> GetDuplicated(T item)
{
return GetOrDefault(item.Slug);
}
/// <inheritdoc /> /// <inheritdoc />
public virtual Task<T?> GetOrDefault(Guid id, Include<T>? include = default) public virtual Task<T?> GetOrDefault(Guid id, Include<T>? include = default)
{ {
@ -303,26 +268,9 @@ public abstract class LocalRepository<T> : IRepository<T>
public virtual async Task<T> Create(T obj) public virtual async Task<T> Create(T obj)
{ {
await Validate(obj); await Validate(obj);
if (obj is IThumbnails thumbs) Database.Entry(obj).State = EntityState.Added;
{ await Database.SaveChangesAsync(() => Get(obj.Slug));
try await IRepository<T>.OnResourceCreated(obj);
{
await _thumbs.DownloadImages(thumbs);
}
catch (DuplicatedItemException e) when (e.Existing is null)
{
throw new DuplicatedItemException(await GetDuplicated(obj));
}
if (thumbs.Poster != null)
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry!.State =
EntityState.Added;
if (thumbs.Thumbnail != null)
Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry!.State =
EntityState.Added;
if (thumbs.Logo != null)
Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry!.State =
EntityState.Added;
}
return obj; return obj;
} }
@ -346,27 +294,11 @@ public abstract class LocalRepository<T> : IRepository<T>
/// <inheritdoc/> /// <inheritdoc/>
public virtual async Task<T> Edit(T edited) public virtual async Task<T> Edit(T edited)
{ {
bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled; await Validate(edited);
Database.ChangeTracker.LazyLoadingEnabled = false; Database.Entry(edited).State = EntityState.Modified;
try await Database.SaveChangesAsync();
{ await IRepository<T>.OnResourceEdited(edited);
T old = await GetWithTracking(edited.Id); return edited;
Merger.Complete(
old,
edited,
x => x.GetCustomAttribute<LoadableRelationAttribute>() == null
);
await EditRelations(old, edited);
await Database.SaveChangesAsync();
await IRepository<T>.OnResourceEdited(old);
return old;
}
finally
{
Database.ChangeTracker.LazyLoadingEnabled = lazyLoading;
Database.ChangeTracker.Clear();
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -391,39 +323,9 @@ public abstract class LocalRepository<T> : IRepository<T>
} }
} }
/// <summary>
/// An overridable method to edit relation of a resource.
/// </summary>
/// <param name="resource">
/// The non edited resource
/// </param>
/// <param name="changed">
/// The new version of <paramref name="resource"/>.
/// This item will be saved on the database and replace <paramref name="resource"/>
/// </param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task EditRelations(T resource, T changed)
{
if (resource is IThumbnails thumbs && changed is IThumbnails chng)
{
Database.Entry(thumbs).Reference(x => x.Poster).IsModified =
thumbs.Poster != chng.Poster;
Database.Entry(thumbs).Reference(x => x.Thumbnail).IsModified =
thumbs.Thumbnail != chng.Thumbnail;
Database.Entry(thumbs).Reference(x => x.Logo).IsModified = thumbs.Logo != chng.Logo;
}
return Validate(resource);
}
/// <summary>
/// A method called just before saving a new resource to the database.
/// It is also called on the default implementation of <see cref="EditRelations"/>
/// </summary>
/// <param name="resource">The resource that will be saved</param>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// You can throw this if the resource is illegal and should not be saved. /// You can throw this if the resource is illegal and should not be saved.
/// </exception> /// </exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task Validate(T resource) protected virtual Task Validate(T resource)
{ {
if ( if (
@ -433,25 +335,8 @@ public abstract class LocalRepository<T> : IRepository<T>
return Task.CompletedTask; return Task.CompletedTask;
if (string.IsNullOrEmpty(resource.Slug)) if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("Resource can't have null as a slug."); throw new ArgumentException("Resource can't have null as a slug.");
if (int.TryParse(resource.Slug, out int _) || resource.Slug == "random") if (resource.Slug == "random")
{ throw new ArgumentException("Resources slug can't be the literal \"random\".");
try
{
MethodInfo? setter = typeof(T).GetProperty(nameof(resource.Slug))!.GetSetMethod();
if (setter != null)
setter.Invoke(resource, new object[] { resource.Slug + '!' });
else
throw new ArgumentException(
"Resources slug can't be number only or the literal \"random\"."
);
}
catch
{
throw new ArgumentException(
"Resources slug can't be number only or the literal \"random\"."
);
}
}
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -470,18 +355,20 @@ public abstract class LocalRepository<T> : IRepository<T>
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual Task Delete(T obj) public virtual async Task Delete(T obj)
{ {
IRepository<T>.OnResourceDeleted(obj); await Database.Set<T>().Where(x => x.Id == obj.Id).ExecuteDeleteAsync();
if (obj is IThumbnails thumbs) await IRepository<T>.OnResourceDeleted(obj);
return _thumbs.DeleteImages(thumbs);
return Task.CompletedTask;
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task DeleteAll(Filter<T> filter) public virtual async Task DeleteAll(Filter<T> filter)
{ {
foreach (T resource in await GetAll(filter)) ICollection<T> items = await GetAll(filter);
await Delete(resource); Guid[] ids = items.Select(x => x.Id).ToArray();
await Database.Set<T>().Where(x => ids.Contains(x.Id)).ExecuteDeleteAsync();
foreach (T resource in items)
await IRepository<T>.OnResourceDeleted(resource);
} }
} }

View File

@ -27,82 +27,29 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers; namespace Kyoo.Core.Controllers;
/// <summary> public class MovieRepository(DatabaseContext database, IRepository<Studio> studios)
/// A local repository to handle shows : LocalRepository<Movie>(database)
/// </summary>
public class MovieRepository : LocalRepository<Movie>
{ {
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
/// A studio repository to handle creation/validation of related studios.
/// </summary>
private readonly IRepository<Studio> _studios;
public MovieRepository(
DatabaseContext database,
IRepository<Studio> studios,
IThumbnailsManager thumbs
)
: base(database, thumbs)
{
_database = database;
_studios = studios;
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Movie>> Search( public override async Task<ICollection<Movie>> Search(
string query, string query,
Include<Movie>? include = default Include<Movie>? include = default
) )
{ {
return await AddIncludes(_database.Movies, include) return await AddIncludes(Database.Movies, include)
.Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
/// <inheritdoc />
public override async Task<Movie> Create(Movie obj)
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
await IRepository<Movie>.OnResourceCreated(obj);
return obj;
}
/// <inheritdoc /> /// <inheritdoc />
protected override async Task Validate(Movie resource) protected override async Task Validate(Movie resource)
{ {
await base.Validate(resource); await base.Validate(resource);
if (resource.Studio != null) if (resource.Studio != null)
{ {
resource.Studio = await _studios.CreateIfNotExists(resource.Studio); resource.Studio = await studios.CreateIfNotExists(resource.Studio);
resource.StudioId = resource.Studio.Id; resource.StudioId = resource.Studio.Id;
} }
} }
/// <inheritdoc />
protected override async Task EditRelations(Movie resource, Movie changed)
{
await Validate(changed);
if (changed.Studio != null || changed.StudioId == null)
{
await Database.Entry(resource).Reference(x => x.Studio).LoadAsync();
resource.Studio = changed.Studio;
}
}
/// <inheritdoc />
public override async Task Delete(Movie obj)
{
_database.Remove(obj);
await _database.SaveChangesAsync();
await base.Delete(obj);
}
} }

View File

@ -31,16 +31,8 @@ using Microsoft.Extensions.DependencyInjection;
namespace Kyoo.Core.Controllers; namespace Kyoo.Core.Controllers;
/// <summary> public class SeasonRepository(DatabaseContext database) : LocalRepository<Season>(database)
/// A local repository to handle seasons.
/// </summary>
public class SeasonRepository : LocalRepository<Season>
{ {
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
static SeasonRepository() static SeasonRepository()
{ {
// Edit seasons slugs when the show's slug changes. // Edit seasons slugs when the show's slug changes.
@ -61,31 +53,13 @@ public class SeasonRepository : LocalRepository<Season>
}; };
} }
/// <summary>
/// Create a new <see cref="SeasonRepository"/>.
/// </summary>
/// <param name="database">The database handle that will be used</param>
/// <param name="thumbs">The thumbnail manager used to store images.</param>
public SeasonRepository(DatabaseContext database, IThumbnailsManager thumbs)
: base(database, thumbs)
{
_database = database;
}
protected override Task<Season?> GetDuplicated(Season item)
{
return _database.Seasons.FirstOrDefaultAsync(x =>
x.ShowId == item.ShowId && x.SeasonNumber == item.SeasonNumber
);
}
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<ICollection<Season>> Search( public override async Task<ICollection<Season>> Search(
string query, string query,
Include<Season>? include = default Include<Season>? include = default
) )
{ {
return await AddIncludes(_database.Seasons, include) return await AddIncludes(Database.Seasons, include)
.Where(x => EF.Functions.ILike(x.Name!, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name!, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
@ -94,14 +68,11 @@ public class SeasonRepository : LocalRepository<Season>
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<Season> Create(Season obj) public override async Task<Season> Create(Season obj)
{ {
await base.Create(obj); // Set it for the OnResourceCreated event and the return value.
obj.ShowSlug = obj.ShowSlug =
(await _database.Shows.FirstOrDefaultAsync(x => x.Id == obj.ShowId))?.Slug (await Database.Shows.FirstOrDefaultAsync(x => x.Id == obj.ShowId))?.Slug
?? throw new ItemNotFoundException($"No show found with ID {obj.ShowId}"); ?? throw new ItemNotFoundException($"No show found with ID {obj.ShowId}");
_database.Entry(obj).State = EntityState.Added; return await base.Create(obj);
await _database.SaveChangesAsync(() => GetDuplicated(obj));
await IRepository<Season>.OnResourceCreated(obj);
return obj;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -120,12 +91,4 @@ public class SeasonRepository : LocalRepository<Season>
resource.ShowId = resource.Show.Id; resource.ShowId = resource.Show.Id;
} }
} }
/// <inheritdoc/>
public override async Task Delete(Season obj)
{
_database.Remove(obj);
await _database.SaveChangesAsync();
await base.Delete(obj);
}
} }

View File

@ -23,87 +23,33 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Utils; using Kyoo.Abstractions.Models.Utils;
using Kyoo.Postgresql; using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers; namespace Kyoo.Core.Controllers;
/// <summary> public class ShowRepository(DatabaseContext database, IRepository<Studio> studios)
/// A local repository to handle shows : LocalRepository<Show>(database)
/// </summary>
public class ShowRepository : LocalRepository<Show>
{ {
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
/// A studio repository to handle creation/validation of related studios.
/// </summary>
private readonly IRepository<Studio> _studios;
public ShowRepository(
DatabaseContext database,
IRepository<Studio> studios,
IThumbnailsManager thumbs
)
: base(database, thumbs)
{
_database = database;
_studios = studios;
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Show>> Search( public override async Task<ICollection<Show>> Search(
string query, string query,
Include<Show>? include = default Include<Show>? include = default
) )
{ {
return await AddIncludes(_database.Shows, include) return await AddIncludes(Database.Shows, include)
.Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
/// <inheritdoc />
public override async Task<Show> Create(Show obj)
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
await IRepository<Show>.OnResourceCreated(obj);
return obj;
}
/// <inheritdoc /> /// <inheritdoc />
protected override async Task Validate(Show resource) protected override async Task Validate(Show resource)
{ {
await base.Validate(resource); await base.Validate(resource);
if (resource.Studio != null) if (resource.Studio != null)
{ {
resource.Studio = await _studios.CreateIfNotExists(resource.Studio); resource.Studio = await studios.CreateIfNotExists(resource.Studio);
resource.StudioId = resource.Studio.Id; resource.StudioId = resource.Studio.Id;
} }
} }
/// <inheritdoc />
protected override async Task EditRelations(Show resource, Show changed)
{
await Validate(changed);
if (changed.Studio != null || changed.StudioId == null)
{
await Database.Entry(resource).Reference(x => x.Studio).LoadAsync();
resource.Studio = changed.Studio;
}
}
/// <inheritdoc />
public override async Task Delete(Show obj)
{
_database.Remove(obj);
await _database.SaveChangesAsync();
await base.Delete(obj);
}
} }

View File

@ -19,11 +19,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Utils; using Kyoo.Abstractions.Models.Utils;
using Kyoo.Postgresql; using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers; namespace Kyoo.Core.Controllers;
@ -31,51 +29,17 @@ namespace Kyoo.Core.Controllers;
/// <summary> /// <summary>
/// A local repository to handle studios /// A local repository to handle studios
/// </summary> /// </summary>
public class StudioRepository : LocalRepository<Studio> public class StudioRepository(DatabaseContext database) : LocalRepository<Studio>(database)
{ {
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
/// Create a new <see cref="StudioRepository"/>.
/// </summary>
/// <param name="database">The database handle</param>
/// <param name="thumbs">The thumbnail manager used to store images.</param>
public StudioRepository(DatabaseContext database, IThumbnailsManager thumbs)
: base(database, thumbs)
{
_database = database;
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Studio>> Search( public override async Task<ICollection<Studio>> Search(
string query, string query,
Include<Studio>? include = default Include<Studio>? include = default
) )
{ {
return await AddIncludes(_database.Studios, include) return await AddIncludes(Database.Studios, include)
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
/// <inheritdoc />
public override async Task<Studio> Create(Studio obj)
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
await IRepository<Studio>.OnResourceCreated(obj);
return obj;
}
/// <inheritdoc />
public override async Task Delete(Studio obj)
{
_database.Entry(obj).State = EntityState.Deleted;
await _database.SaveChangesAsync();
await base.Delete(obj);
}
} }

View File

@ -40,9 +40,8 @@ public class UserRepository(
DatabaseContext database, DatabaseContext database,
DbConnection db, DbConnection db,
SqlVariableContext context, SqlVariableContext context,
IThumbnailsManager thumbs,
PermissionOption options PermissionOption options
) : LocalRepository<User>(database, thumbs), IUserRepository ) : LocalRepository<User>(database), IUserRepository
{ {
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<User>> Search( public override async Task<ICollection<User>> Search(
@ -50,7 +49,7 @@ public class UserRepository(
Include<User>? include = default Include<User>? include = default
) )
{ {
return await AddIncludes(database.Users, include) return await AddIncludes(Database.Users, include)
.Where(x => EF.Functions.ILike(x.Username, $"%{query}%")) .Where(x => EF.Functions.ILike(x.Username, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
@ -60,26 +59,14 @@ public class UserRepository(
public override async Task<User> Create(User obj) public override async Task<User> Create(User obj)
{ {
// If no users exists, the new one will be an admin. Give it every permissions. // If no users exists, the new one will be an admin. Give it every permissions.
if (!await database.Users.AnyAsync()) if (!await Database.Users.AnyAsync())
obj.Permissions = PermissionOption.Admin; obj.Permissions = PermissionOption.Admin;
else if (!options.RequireVerification) else if (!options.RequireVerification)
obj.Permissions = options.NewUser; obj.Permissions = options.NewUser;
else else
obj.Permissions = Array.Empty<string>(); obj.Permissions = Array.Empty<string>();
await base.Create(obj); return await base.Create(obj);
database.Entry(obj).State = EntityState.Added;
await database.SaveChangesAsync(() => Get(obj.Slug));
await IRepository<User>.OnResourceCreated(obj);
return obj;
}
/// <inheritdoc />
public override async Task Delete(User obj)
{
database.Entry(obj).State = EntityState.Deleted;
await database.SaveChangesAsync();
await base.Delete(obj);
} }
public Task<User?> GetByExternalId(string provider, string id) public Task<User?> GetByExternalId(string provider, string id)
@ -109,8 +96,8 @@ public class UserRepository(
User user = await GetWithTracking(userId); User user = await GetWithTracking(userId);
user.ExternalId[provider] = token; user.ExternalId[provider] = token;
// without that, the change tracker does not find the modification. /shrug // without that, the change tracker does not find the modification. /shrug
database.Entry(user).Property(x => x.ExternalId).IsModified = true; Database.Entry(user).Property(x => x.ExternalId).IsModified = true;
await database.SaveChangesAsync(); await Database.SaveChangesAsync();
return user; return user;
} }
@ -119,8 +106,8 @@ public class UserRepository(
User user = await GetWithTracking(userId); User user = await GetWithTracking(userId);
user.ExternalId.Remove(provider); user.ExternalId.Remove(provider);
// without that, the change tracker does not find the modification. /shrug // without that, the change tracker does not find the modification. /shrug
database.Entry(user).Property(x => x.ExternalId).IsModified = true; Database.Entry(user).Property(x => x.ExternalId).IsModified = true;
await database.SaveChangesAsync(); await Database.SaveChangesAsync();
return user; return user;
} }
} }

View File

@ -16,9 +16,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using AspNetCore.Proxy; using AspNetCore.Proxy;

View File

@ -16,9 +16,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Meilisearch; using Meilisearch;