mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Implement a base repository for dapper
This commit is contained in:
parent
179b79c926
commit
ba37786038
@ -174,19 +174,19 @@ namespace Kyoo.Abstractions.Models
|
||||
// language=PostgreSQL
|
||||
Sql = """
|
||||
select
|
||||
"pe".* -- Episode as pe
|
||||
pe.* -- Episode as pe
|
||||
from
|
||||
episodes as "pe"
|
||||
where
|
||||
"pe".show_id = "this".show_id
|
||||
and ("pe".absolute_number < "this".absolute_number
|
||||
or "pe".season_number < "this".season_number
|
||||
or ("pe".season_number = "this".season_number
|
||||
pe.show_id = "this".show_id
|
||||
and (pe.absolute_number < "this".absolute_number
|
||||
or pe.season_number < "this".season_number
|
||||
or (pe.season_number = "this".season_number
|
||||
and e.episode_number < "this".episode_number))
|
||||
order by
|
||||
"pe".absolute_number desc,
|
||||
"pe".season_number desc,
|
||||
"pe".episode_number desc
|
||||
pe.absolute_number desc,
|
||||
pe.season_number desc,
|
||||
pe.episode_number desc
|
||||
limit 1
|
||||
"""
|
||||
)]
|
||||
@ -210,19 +210,19 @@ namespace Kyoo.Abstractions.Models
|
||||
// language=PostgreSQL
|
||||
Sql = """
|
||||
select
|
||||
"ne".* -- Episode as ne
|
||||
ne.* -- Episode as ne
|
||||
from
|
||||
episodes as "ne"
|
||||
where
|
||||
"ne".show_id = "this".show_id
|
||||
and ("ne".absolute_number > "this".absolute_number
|
||||
or "ne".season_number > "this".season_number
|
||||
or ("ne".season_number = "this".season_number
|
||||
ne.show_id = "this".show_id
|
||||
and (ne.absolute_number > "this".absolute_number
|
||||
or ne.season_number > "this".season_number
|
||||
or (ne.season_number = "this".season_number
|
||||
and e.episode_number > "this".episode_number))
|
||||
order by
|
||||
"ne".absolute_number,
|
||||
"ne".season_number,
|
||||
"ne".episode_number
|
||||
ne.absolute_number,
|
||||
ne.season_number,
|
||||
ne.episode_number
|
||||
limit 1
|
||||
"""
|
||||
)]
|
||||
|
@ -158,7 +158,7 @@ namespace Kyoo.Abstractions.Models
|
||||
// language=PostgreSQL
|
||||
Sql = """
|
||||
select
|
||||
"fe".* -- Episode as fe
|
||||
fe.* -- Episode as fe
|
||||
from (
|
||||
select
|
||||
e.*,
|
||||
@ -166,7 +166,7 @@ namespace Kyoo.Abstractions.Models
|
||||
from
|
||||
episodes as e) as "fe"
|
||||
where
|
||||
"fe".number <= 1
|
||||
fe.number <= 1
|
||||
""",
|
||||
On = "show_id = \"this\".id"
|
||||
)]
|
||||
|
@ -48,11 +48,9 @@ public static class DapperHelper
|
||||
return $"coalesce({string.Join(", ", keys)})";
|
||||
}
|
||||
|
||||
public static string ProcessSort<T>(Sort<T>? sort, bool reverse, Dictionary<string, Type> config, bool recurse = false)
|
||||
public static string ProcessSort<T>(Sort<T> sort, bool reverse, Dictionary<string, Type> config, bool recurse = false)
|
||||
where T : IQuery
|
||||
{
|
||||
sort ??= new Sort<T>.Default();
|
||||
|
||||
string ret = sort switch
|
||||
{
|
||||
Sort<T>.Default(var value) => ProcessSort(value, reverse, config, true),
|
||||
@ -78,7 +76,7 @@ public static class DapperHelper
|
||||
Dictionary<string, Type> retConfig = new();
|
||||
StringBuilder join = new();
|
||||
|
||||
foreach (Include<T>.Metadata metadata in include.Metadatas)
|
||||
foreach (Include.Metadata metadata in include.Metadatas)
|
||||
{
|
||||
relation++;
|
||||
switch (metadata)
|
||||
@ -103,7 +101,7 @@ public static class DapperHelper
|
||||
}
|
||||
}
|
||||
|
||||
T Map(T item, IEnumerable<object> relations)
|
||||
T Map(T item, IEnumerable<object?> relations)
|
||||
{
|
||||
foreach ((string name, object? value) in include.Fields.Zip(relations))
|
||||
{
|
||||
@ -177,7 +175,7 @@ public static class DapperHelper
|
||||
this IDbConnection db,
|
||||
FormattableString command,
|
||||
Dictionary<string, Type> config,
|
||||
Func<object?[], T> mapper,
|
||||
Func<List<object?>, T> mapper,
|
||||
Func<int, Task<T>> get,
|
||||
Include<T>? include,
|
||||
Filter<T>? filter,
|
||||
@ -205,7 +203,8 @@ public static class DapperHelper
|
||||
}
|
||||
if (filter != null)
|
||||
query += ProcessFilter(filter, config);
|
||||
query += $"\norder by {ProcessSort(sort, limit.Reverse, config):raw}";
|
||||
if (sort != null)
|
||||
query += $"\norder by {ProcessSort(sort, limit.Reverse, config):raw}";
|
||||
query += $"\nlimit {limit.Limit}";
|
||||
|
||||
// Build query and prepare to do the query/projections
|
||||
@ -260,7 +259,7 @@ public static class DapperHelper
|
||||
thumbs.Thumbnail = items[++i] as Image;
|
||||
thumbs.Logo = items[++i] as Image;
|
||||
}
|
||||
return mapIncludes(mapper(nItems.ToArray()), nItems.Skip(config.Count));
|
||||
return mapIncludes(mapper(nItems), nItems.Skip(config.Count));
|
||||
},
|
||||
ParametersDictionary.LoadFrom(cmd),
|
||||
splitOn: string.Join(',', types.Select(x => x == typeof(Image) ? "source" : "id"))
|
||||
@ -269,4 +268,40 @@ public static class DapperHelper
|
||||
data = data.Reverse();
|
||||
return data.ToList();
|
||||
}
|
||||
|
||||
public static async Task<T?> QuerySingle<T>(
|
||||
this IDbConnection db,
|
||||
FormattableString command,
|
||||
Dictionary<string, Type> config,
|
||||
Func<List<object?>, T> mapper,
|
||||
Include<T>? include,
|
||||
Filter<T>? filter,
|
||||
Sort<T>? sort = null)
|
||||
where T : class, IResource, IQuery
|
||||
{
|
||||
ICollection<T> ret = await db.Query<T>(command, config, mapper, null!, include, filter, sort, new Pagination(1));
|
||||
return ret.FirstOrDefault();
|
||||
}
|
||||
|
||||
public static async Task<int> Count<T>(
|
||||
this IDbConnection db,
|
||||
FormattableString command,
|
||||
Dictionary<string, Type> config,
|
||||
Filter<T>? filter)
|
||||
where T : class, IResource
|
||||
{
|
||||
InterpolatedSql.Dapper.SqlBuilders.SqlBuilder query = new(db, command);
|
||||
|
||||
if (filter != null)
|
||||
query += ProcessFilter(filter, config);
|
||||
|
||||
IDapperSqlCommand cmd = query.Build();
|
||||
// language=postgreSQL
|
||||
string sql = $"select count(*) from ({cmd.Sql})";
|
||||
|
||||
return await db.ExecuteAsync(
|
||||
sql,
|
||||
ParametersDictionary.LoadFrom(cmd)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -18,87 +18,156 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Data.Common;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Utils;
|
||||
|
||||
namespace Kyoo.Core.Controllers;
|
||||
|
||||
public class DapperRepository<T> : IRepository<T>
|
||||
public abstract class DapperRepository<T> : IRepository<T>
|
||||
where T : class, IResource, IQuery
|
||||
{
|
||||
public Type RepositoryType => typeof(T);
|
||||
|
||||
protected abstract FormattableString Sql { get; }
|
||||
|
||||
protected abstract Dictionary<string, Type> Config { get; }
|
||||
|
||||
protected abstract T Mapper(List<object?> items);
|
||||
|
||||
protected DbConnection Database { get; init; }
|
||||
|
||||
public DapperRepository(DbConnection database)
|
||||
{
|
||||
Database = database;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Get(int id, Include<T>? include = default)
|
||||
{
|
||||
T? ret = await GetOrDefault(id, include);
|
||||
if (ret == null)
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Get(string slug, Include<T>? include = default)
|
||||
{
|
||||
T? ret = await GetOrDefault(slug, include);
|
||||
if (ret == null)
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the slug {slug}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<T> Get(Filter<T> filter,
|
||||
Include<T>? include = default)
|
||||
{
|
||||
T? ret = await GetOrDefault(filter, include: include);
|
||||
if (ret == null)
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<T>> FromIds(IList<int> ids, Include<T>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T> Get(int id, Include<T>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T> Get(string slug, Include<T>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T> Get(Filter<T> filter, Include<T>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ICollection<T>> GetAll(Filter<T>? filter = null, Sort<T>? sort = null, Include<T>? include = null, Pagination? limit = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<int> GetCount(Filter<T>? filter = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T?> GetOrDefault(int id, Include<T>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Database.QuerySingle<T>(
|
||||
Sql,
|
||||
Config,
|
||||
Mapper,
|
||||
include,
|
||||
new Filter<T>.Eq(nameof(IResource.Id), id)
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T?> GetOrDefault(string slug, Include<T>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Database.QuerySingle<T>(
|
||||
Sql,
|
||||
Config,
|
||||
Mapper,
|
||||
include,
|
||||
new Filter<T>.Eq(nameof(IResource.Slug), slug)
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T?> GetOrDefault(Filter<T>? filter, Include<T>? include = null, Sort<T>? sortBy = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Database.QuerySingle<T>(
|
||||
Sql,
|
||||
Config,
|
||||
Mapper,
|
||||
include,
|
||||
filter,
|
||||
sortBy
|
||||
);
|
||||
}
|
||||
|
||||
public Task<ICollection<T>> Search(string query, Include<T>? include = null)
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<T>> GetAll(Filter<T>? filter = default,
|
||||
Sort<T>? sort = default,
|
||||
Include<T>? include = default,
|
||||
Pagination? limit = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Database.Query<T>(
|
||||
Sql,
|
||||
Config,
|
||||
Mapper,
|
||||
(id) => Get(id),
|
||||
include,
|
||||
filter,
|
||||
sort ?? new Sort<T>.Default(),
|
||||
limit ?? new()
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> GetCount(Filter<T>? filter = null)
|
||||
{
|
||||
return Database.Count(
|
||||
Sql,
|
||||
Config,
|
||||
filter
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<T>> Search(string query, Include<T>? include = null) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> Create(T obj) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> CreateIfNotExists(T obj) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(int id) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string slug) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(T obj) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteAll(Filter<T> filter) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> Edit(T edited) => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<T> Patch(int id, Func<T, Task<bool>> patch) => throw new NotImplementedException();
|
||||
}
|
||||
|
@ -18,157 +18,63 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using InterpolatedSql.Dapper;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Utils;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle library items.
|
||||
/// </summary>
|
||||
public class LibraryItemRepository : IRepository<ILibraryItem>
|
||||
public class LibraryItemRepository : DapperRepository<ILibraryItem>
|
||||
{
|
||||
private readonly DbConnection _database;
|
||||
// language=PostgreSQL
|
||||
protected override FormattableString Sql => $"""
|
||||
select
|
||||
s.*, -- Show as s
|
||||
m.*,
|
||||
c.*
|
||||
/* includes */
|
||||
from
|
||||
shows as s
|
||||
full outer join (
|
||||
select
|
||||
* -- Movie
|
||||
from
|
||||
movies) as m on false
|
||||
full outer join(
|
||||
select
|
||||
* -- Collection
|
||||
from
|
||||
collections) as c on false
|
||||
""";
|
||||
|
||||
public Type RepositoryType => typeof(ILibraryItem);
|
||||
protected override Dictionary<string, Type> Config => new()
|
||||
{
|
||||
{ "s", typeof(Show) },
|
||||
{ "m", typeof(Movie) },
|
||||
{ "c", typeof(Collection) }
|
||||
};
|
||||
|
||||
protected override ILibraryItem Mapper(List<object?> items)
|
||||
{
|
||||
if (items[0] is Show show && show.Id != 0)
|
||||
return show;
|
||||
if (items[1] is Movie movie && movie.Id != 0)
|
||||
return movie;
|
||||
if (items[2] is Collection collection && collection.Id != 0)
|
||||
return collection;
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
public LibraryItemRepository(DbConnection database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<ILibraryItem> Get(int id, Include<ILibraryItem>? include = default)
|
||||
{
|
||||
ILibraryItem? ret = await GetOrDefault(id, include);
|
||||
if (ret == null)
|
||||
throw new ItemNotFoundException($"No {nameof(ILibraryItem)} found with the id {id}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<ILibraryItem> Get(string slug, Include<ILibraryItem>? include = default)
|
||||
{
|
||||
ILibraryItem? ret = await GetOrDefault(slug, include);
|
||||
if (ret == null)
|
||||
throw new ItemNotFoundException($"No {nameof(ILibraryItem)} found with the slug {slug}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<ILibraryItem> Get(Filter<ILibraryItem> filter,
|
||||
Include<ILibraryItem>? include = default)
|
||||
{
|
||||
ILibraryItem? ret = await GetOrDefault(filter, include: include);
|
||||
if (ret == null)
|
||||
throw new ItemNotFoundException($"No {nameof(ILibraryItem)} found with the given predicate.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Task<ILibraryItem?> GetOrDefault(int id, Include<ILibraryItem>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ILibraryItem?> GetOrDefault(string slug, Include<ILibraryItem>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ILibraryItem?> GetOrDefault(Filter<ILibraryItem>? filter, Include<ILibraryItem>? include = default,
|
||||
Sort<ILibraryItem>? sortBy = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ICollection<ILibraryItem>> GetAll(
|
||||
Filter<ILibraryItem>? filter = null,
|
||||
Sort<ILibraryItem>? sort = default,
|
||||
Include<ILibraryItem>? include = default,
|
||||
Pagination? limit = default)
|
||||
{
|
||||
// language=PostgreSQL
|
||||
FormattableString sql = $"""
|
||||
select
|
||||
s.*, -- Show as s
|
||||
m.*,
|
||||
c.*
|
||||
/* includes */
|
||||
from
|
||||
shows as s
|
||||
full outer join (
|
||||
select
|
||||
* -- Movie
|
||||
from
|
||||
movies) as m on false
|
||||
full outer join (
|
||||
select
|
||||
* -- Collection
|
||||
from
|
||||
collections) as c on false
|
||||
""";
|
||||
|
||||
return _database.Query<ILibraryItem>(sql, new()
|
||||
{
|
||||
{ "s", typeof(Show) },
|
||||
{ "m", typeof(Movie) },
|
||||
{ "c", typeof(Collection) }
|
||||
},
|
||||
items =>
|
||||
{
|
||||
if (items[0] is Show show && show.Id != 0)
|
||||
return show;
|
||||
if (items[1] is Movie movie && movie.Id != 0)
|
||||
return movie;
|
||||
if (items[2] is Collection collection && collection.Id != 0)
|
||||
return collection;
|
||||
throw new InvalidDataException();
|
||||
},
|
||||
(id) => Get(id),
|
||||
include, filter, sort, limit ?? new()
|
||||
);
|
||||
}
|
||||
|
||||
public Task<int> GetCount(Filter<ILibraryItem>? filter = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<ICollection<ILibraryItem>> FromIds(IList<int> ids, Include<ILibraryItem>? include = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task DeleteAll(Filter<ILibraryItem> filter)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<ILibraryItem>> Search(string query, Include<ILibraryItem>? include = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// return await Sort(
|
||||
// AddIncludes(_database.LibraryItems, include)
|
||||
// .Where(_database.Like<LibraryItem>(x => x.Name, $"%{query}%"))
|
||||
// )
|
||||
// .Take(20)
|
||||
// .ToListAsync();
|
||||
}
|
||||
: base(database)
|
||||
{ }
|
||||
|
||||
public async Task<ICollection<ILibraryItem>> GetAllOfCollection(
|
||||
Expression<Func<Collection, bool>> selector,
|
||||
@ -193,33 +99,5 @@ namespace Kyoo.Core.Controllers
|
||||
// limit,
|
||||
// include);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ILibraryItem> Create(ILibraryItem obj)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ILibraryItem> CreateIfNotExists(ILibraryItem obj)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ILibraryItem> Edit(ILibraryItem edited)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ILibraryItem> Patch(int id, Func<ILibraryItem, Task<bool>> patch)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(int id)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string slug)
|
||||
=> throw new InvalidOperationException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(ILibraryItem obj)
|
||||
=> throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user