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>
/// A local repository to handle collections
/// </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 />
public override async Task<ICollection<Collection>> Search(
string query,
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}%"))
.Take(20)
.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 />
protected override async Task Validate(Collection resource)
{
@ -82,21 +56,13 @@ public class CollectionRepository : LocalRepository<Collection>
public async Task AddMovie(Guid id, Guid movieId)
{
_database.AddLinks<Collection, Movie>(id, movieId);
await _database.SaveChangesAsync();
Database.AddLinks<Collection, Movie>(id, movieId);
await Database.SaveChangesAsync();
}
public async Task AddShow(Guid id, Guid showId)
{
_database.AddLinks<Collection, Show>(id, showId);
await _database.SaveChangesAsync();
}
/// <inheritdoc />
public override async Task Delete(Collection obj)
{
_database.Entry(obj).State = EntityState.Deleted;
await _database.SaveChangesAsync();
await base.Delete(obj);
Database.AddLinks<Collection, Show>(id, showId);
await Database.SaveChangesAsync();
}
}

View File

@ -32,11 +32,8 @@ namespace Kyoo.Core.Controllers;
/// <summary>
/// A local repository to handle episodes.
/// </summary>
public class EpisodeRepository(
DatabaseContext database,
IRepository<Show> shows,
IThumbnailsManager thumbs
) : LocalRepository<Episode>(database, thumbs)
public class EpisodeRepository(DatabaseContext database, IRepository<Show> shows)
: LocalRepository<Episode>(database)
{
static EpisodeRepository()
{
@ -64,34 +61,18 @@ public class EpisodeRepository(
Include<Episode>? include = default
)
{
return await AddIncludes(database.Episodes, include)
return await AddIncludes(Database.Episodes, include)
.Where(x => EF.Functions.ILike(x.Name!, $"%{query}%"))
.Take(20)
.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 />
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;
await base.Create(obj);
database.Entry(obj).State = EntityState.Added;
await database.SaveChangesAsync(() => GetDuplicated(obj));
await IRepository<Episode>.OnResourceCreated(obj);
return obj;
return await base.Create(obj);
}
/// <inheritdoc />
@ -111,7 +92,7 @@ public class EpisodeRepository(
}
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
);
}
@ -120,14 +101,40 @@ public class EpisodeRepository(
/// <inheritdoc />
public override async Task Delete(Episode obj)
{
int epCount = await database
int epCount = await Database
.Episodes.Where(x => x.ShowId == obj.ShowId)
.Take(2)
.CountAsync();
database.Entry(obj).State = EntityState.Deleted;
await database.SaveChangesAsync();
await base.Delete(obj);
if (epCount == 1)
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.Utils;
using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers;
/// <summary>
/// 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>
public abstract class LocalRepository<T>(DatabaseContext database) : IRepository<T>
where T : class, IResource, IQuery
{
/// <summary>
/// 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;
}
public DatabaseContext Database => database;
/// <inheritdoc/>
public Type RepositoryType => typeof(T);
@ -127,12 +103,6 @@ public abstract class LocalRepository<T> : IRepository<T>
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)
{
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;
}
protected virtual Task<T?> GetDuplicated(T item)
{
return GetOrDefault(item.Slug);
}
/// <inheritdoc />
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)
{
await Validate(obj);
if (obj is IThumbnails thumbs)
{
try
{
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;
}
Database.Entry(obj).State = EntityState.Added;
await Database.SaveChangesAsync(() => Get(obj.Slug));
await IRepository<T>.OnResourceCreated(obj);
return obj;
}
@ -346,27 +294,11 @@ public abstract class LocalRepository<T> : IRepository<T>
/// <inheritdoc/>
public virtual async Task<T> Edit(T edited)
{
bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled;
Database.ChangeTracker.LazyLoadingEnabled = false;
try
{
T old = await GetWithTracking(edited.Id);
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();
}
await Validate(edited);
Database.Entry(edited).State = EntityState.Modified;
await Database.SaveChangesAsync();
await IRepository<T>.OnResourceEdited(edited);
return edited;
}
/// <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">
/// You can throw this if the resource is illegal and should not be saved.
/// </exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected virtual Task Validate(T resource)
{
if (
@ -433,25 +335,8 @@ public abstract class LocalRepository<T> : IRepository<T>
return Task.CompletedTask;
if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("Resource can't have null as a slug.");
if (int.TryParse(resource.Slug, out int _) || resource.Slug == "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\"."
);
}
}
if (resource.Slug == "random")
throw new ArgumentException("Resources slug can't be the literal \"random\".");
return Task.CompletedTask;
}
@ -470,18 +355,20 @@ public abstract class LocalRepository<T> : IRepository<T>
}
/// <inheritdoc/>
public virtual Task Delete(T obj)
public virtual async Task Delete(T obj)
{
IRepository<T>.OnResourceDeleted(obj);
if (obj is IThumbnails thumbs)
return _thumbs.DeleteImages(thumbs);
return Task.CompletedTask;
await Database.Set<T>().Where(x => x.Id == obj.Id).ExecuteDeleteAsync();
await IRepository<T>.OnResourceDeleted(obj);
}
/// <inheritdoc/>
public async Task DeleteAll(Filter<T> filter)
public virtual async Task DeleteAll(Filter<T> filter)
{
foreach (T resource in await GetAll(filter))
await Delete(resource);
ICollection<T> items = await GetAll(filter);
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;
/// <summary>
/// A local repository to handle shows
/// </summary>
public class MovieRepository : LocalRepository<Movie>
public class MovieRepository(DatabaseContext database, IRepository<Studio> studios)
: LocalRepository<Movie>(database)
{
/// <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 />
public override async Task<ICollection<Movie>> Search(
string query,
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}%"))
.Take(20)
.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 />
protected override async Task Validate(Movie resource)
{
await base.Validate(resource);
if (resource.Studio != null)
{
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
resource.Studio = await studios.CreateIfNotExists(resource.Studio);
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;
/// <summary>
/// A local repository to handle seasons.
/// </summary>
public class SeasonRepository : LocalRepository<Season>
public class SeasonRepository(DatabaseContext database) : LocalRepository<Season>(database)
{
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
static SeasonRepository()
{
// 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/>
public override async Task<ICollection<Season>> Search(
string query,
Include<Season>? include = default
)
{
return await AddIncludes(_database.Seasons, include)
return await AddIncludes(Database.Seasons, include)
.Where(x => EF.Functions.ILike(x.Name!, $"%{query}%"))
.Take(20)
.ToListAsync();
@ -94,14 +68,11 @@ public class SeasonRepository : LocalRepository<Season>
/// <inheritdoc/>
public override async Task<Season> Create(Season obj)
{
await base.Create(obj);
// Set it for the OnResourceCreated event and the return value.
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}");
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => GetDuplicated(obj));
await IRepository<Season>.OnResourceCreated(obj);
return obj;
return await base.Create(obj);
}
/// <inheritdoc/>
@ -120,12 +91,4 @@ public class SeasonRepository : LocalRepository<Season>
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.Utils;
using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers;
/// <summary>
/// A local repository to handle shows
/// </summary>
public class ShowRepository : LocalRepository<Show>
public class ShowRepository(DatabaseContext database, IRepository<Studio> studios)
: LocalRepository<Show>(database)
{
/// <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 />
public override async Task<ICollection<Show>> Search(
string query,
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}%"))
.Take(20)
.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 />
protected override async Task Validate(Show resource)
{
await base.Validate(resource);
if (resource.Studio != null)
{
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
resource.Studio = await studios.CreateIfNotExists(resource.Studio);
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.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers;
@ -31,51 +29,17 @@ namespace Kyoo.Core.Controllers;
/// <summary>
/// A local repository to handle studios
/// </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 />
public override async Task<ICollection<Studio>> Search(
string query,
Include<Studio>? include = default
)
{
return await AddIncludes(_database.Studios, include)
return await AddIncludes(Database.Studios, include)
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20)
.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,
DbConnection db,
SqlVariableContext context,
IThumbnailsManager thumbs,
PermissionOption options
) : LocalRepository<User>(database, thumbs), IUserRepository
) : LocalRepository<User>(database), IUserRepository
{
/// <inheritdoc />
public override async Task<ICollection<User>> Search(
@ -50,7 +49,7 @@ public class UserRepository(
Include<User>? include = default
)
{
return await AddIncludes(database.Users, include)
return await AddIncludes(Database.Users, include)
.Where(x => EF.Functions.ILike(x.Username, $"%{query}%"))
.Take(20)
.ToListAsync();
@ -60,26 +59,14 @@ public class UserRepository(
public override async Task<User> Create(User obj)
{
// 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;
else if (!options.RequireVerification)
obj.Permissions = options.NewUser;
else
obj.Permissions = Array.Empty<string>();
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);
return await base.Create(obj);
}
public Task<User?> GetByExternalId(string provider, string id)
@ -109,8 +96,8 @@ public class UserRepository(
User user = await GetWithTracking(userId);
user.ExternalId[provider] = token;
// without that, the change tracker does not find the modification. /shrug
database.Entry(user).Property(x => x.ExternalId).IsModified = true;
await database.SaveChangesAsync();
Database.Entry(user).Property(x => x.ExternalId).IsModified = true;
await Database.SaveChangesAsync();
return user;
}
@ -119,8 +106,8 @@ public class UserRepository(
User user = await GetWithTracking(userId);
user.ExternalId.Remove(provider);
// without that, the change tracker does not find the modification. /shrug
database.Entry(user).Property(x => x.ExternalId).IsModified = true;
await database.SaveChangesAsync();
Database.Entry(user).Property(x => x.ExternalId).IsModified = true;
await Database.SaveChangesAsync();
return user;
}
}

View File

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

View File

@ -16,9 +16,6 @@
// You should have received a copy of the GNU General Public License
// 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.Models;
using Meilisearch;