mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-23 15:30:34 -04:00
Update csharpier
This commit is contained in:
parent
042cc018cb
commit
a5638203a6
@ -9,7 +9,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"csharpier": {
|
"csharpier": {
|
||||||
"version": "0.26.4",
|
"version": "0.27.2",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-csharpier"
|
"dotnet-csharpier"
|
||||||
]
|
]
|
||||||
|
@ -181,37 +181,35 @@ namespace Kyoo.Abstractions.Models
|
|||||||
[LoadableRelation(
|
[LoadableRelation(
|
||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
Sql = """
|
Sql = """
|
||||||
select
|
select
|
||||||
pe.* -- Episode as pe
|
pe.* -- Episode as pe
|
||||||
from
|
from
|
||||||
episodes as "pe"
|
episodes as "pe"
|
||||||
where
|
where
|
||||||
pe.show_id = "this".show_id
|
pe.show_id = "this".show_id
|
||||||
and (pe.absolute_number < "this".absolute_number
|
and (pe.absolute_number < "this".absolute_number
|
||||||
or pe.season_number < "this".season_number
|
or pe.season_number < "this".season_number
|
||||||
or (pe.season_number = "this".season_number
|
or (pe.season_number = "this".season_number
|
||||||
and e.episode_number < "this".episode_number))
|
and e.episode_number < "this".episode_number))
|
||||||
order by
|
order by
|
||||||
pe.absolute_number desc nulls last,
|
pe.absolute_number desc nulls last,
|
||||||
pe.season_number desc,
|
pe.season_number desc,
|
||||||
pe.episode_number desc
|
pe.episode_number desc
|
||||||
limit 1
|
limit 1
|
||||||
"""
|
"""
|
||||||
)]
|
)]
|
||||||
public Episode? PreviousEpisode { get; set; }
|
public Episode? PreviousEpisode { get; set; }
|
||||||
|
|
||||||
private Episode? _PreviousEpisode =>
|
private Episode? _PreviousEpisode =>
|
||||||
Show!
|
Show!
|
||||||
.Episodes!
|
.Episodes!.OrderBy(x => x.AbsoluteNumber == null)
|
||||||
.OrderBy(x => x.AbsoluteNumber == null)
|
|
||||||
.ThenByDescending(x => x.AbsoluteNumber)
|
.ThenByDescending(x => x.AbsoluteNumber)
|
||||||
.ThenByDescending(x => x.SeasonNumber)
|
.ThenByDescending(x => x.SeasonNumber)
|
||||||
.ThenByDescending(x => x.EpisodeNumber)
|
.ThenByDescending(x => x.EpisodeNumber)
|
||||||
.FirstOrDefault(
|
.FirstOrDefault(x =>
|
||||||
x =>
|
x.AbsoluteNumber < AbsoluteNumber
|
||||||
x.AbsoluteNumber < AbsoluteNumber
|
|| x.SeasonNumber < SeasonNumber
|
||||||
|| x.SeasonNumber < SeasonNumber
|
|| (x.SeasonNumber == SeasonNumber && x.EpisodeNumber < EpisodeNumber)
|
||||||
|| (x.SeasonNumber == SeasonNumber && x.EpisodeNumber < EpisodeNumber)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -221,36 +219,34 @@ namespace Kyoo.Abstractions.Models
|
|||||||
[LoadableRelation(
|
[LoadableRelation(
|
||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
Sql = """
|
Sql = """
|
||||||
select
|
select
|
||||||
ne.* -- Episode as ne
|
ne.* -- Episode as ne
|
||||||
from
|
from
|
||||||
episodes as "ne"
|
episodes as "ne"
|
||||||
where
|
where
|
||||||
ne.show_id = "this".show_id
|
ne.show_id = "this".show_id
|
||||||
and (ne.absolute_number > "this".absolute_number
|
and (ne.absolute_number > "this".absolute_number
|
||||||
or ne.season_number > "this".season_number
|
or ne.season_number > "this".season_number
|
||||||
or (ne.season_number = "this".season_number
|
or (ne.season_number = "this".season_number
|
||||||
and e.episode_number > "this".episode_number))
|
and e.episode_number > "this".episode_number))
|
||||||
order by
|
order by
|
||||||
ne.absolute_number,
|
ne.absolute_number,
|
||||||
ne.season_number,
|
ne.season_number,
|
||||||
ne.episode_number
|
ne.episode_number
|
||||||
limit 1
|
limit 1
|
||||||
"""
|
"""
|
||||||
)]
|
)]
|
||||||
public Episode? NextEpisode { get; set; }
|
public Episode? NextEpisode { get; set; }
|
||||||
|
|
||||||
private Episode? _NextEpisode =>
|
private Episode? _NextEpisode =>
|
||||||
Show!
|
Show!
|
||||||
.Episodes!
|
.Episodes!.OrderBy(x => x.AbsoluteNumber)
|
||||||
.OrderBy(x => x.AbsoluteNumber)
|
|
||||||
.ThenBy(x => x.SeasonNumber)
|
.ThenBy(x => x.SeasonNumber)
|
||||||
.ThenBy(x => x.EpisodeNumber)
|
.ThenBy(x => x.EpisodeNumber)
|
||||||
.FirstOrDefault(
|
.FirstOrDefault(x =>
|
||||||
x =>
|
x.AbsoluteNumber > AbsoluteNumber
|
||||||
x.AbsoluteNumber > AbsoluteNumber
|
|| x.SeasonNumber > SeasonNumber
|
||||||
|| x.SeasonNumber > SeasonNumber
|
|| (x.SeasonNumber == SeasonNumber && x.EpisodeNumber > EpisodeNumber)
|
||||||
|| (x.SeasonNumber == SeasonNumber && x.EpisodeNumber > EpisodeNumber)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
[SerializeIgnore]
|
[SerializeIgnore]
|
||||||
|
@ -135,14 +135,14 @@ namespace Kyoo.Abstractions.Models
|
|||||||
[LoadableRelation(
|
[LoadableRelation(
|
||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
Projected = """
|
Projected = """
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
count(*)::int
|
count(*)::int
|
||||||
from
|
from
|
||||||
episodes as e
|
episodes as e
|
||||||
where
|
where
|
||||||
e.season_id = id) as episodes_count
|
e.season_id = id) as episodes_count
|
||||||
"""
|
"""
|
||||||
)]
|
)]
|
||||||
public int EpisodesCount { get; set; }
|
public int EpisodesCount { get; set; }
|
||||||
|
|
||||||
|
@ -170,17 +170,17 @@ namespace Kyoo.Abstractions.Models
|
|||||||
[LoadableRelation(
|
[LoadableRelation(
|
||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
Sql = """
|
Sql = """
|
||||||
select
|
|
||||||
fe.* -- Episode as fe
|
|
||||||
from (
|
|
||||||
select
|
select
|
||||||
e.*,
|
fe.* -- Episode as fe
|
||||||
row_number() over (partition by e.show_id order by e.absolute_number, e.season_number, e.episode_number) as number
|
from (
|
||||||
from
|
select
|
||||||
episodes as e) as "fe"
|
e.*,
|
||||||
where
|
row_number() over (partition by e.show_id order by e.absolute_number, e.season_number, e.episode_number) as number
|
||||||
fe.number <= 1
|
from
|
||||||
""",
|
episodes as e) as "fe"
|
||||||
|
where
|
||||||
|
fe.number <= 1
|
||||||
|
""",
|
||||||
On = "show_id = \"this\".id"
|
On = "show_id = \"this\".id"
|
||||||
)]
|
)]
|
||||||
public Episode? FirstEpisode { get; set; }
|
public Episode? FirstEpisode { get; set; }
|
||||||
@ -200,14 +200,14 @@ namespace Kyoo.Abstractions.Models
|
|||||||
[LoadableRelation(
|
[LoadableRelation(
|
||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
Projected = """
|
Projected = """
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
count(*)::int
|
count(*)::int
|
||||||
from
|
from
|
||||||
episodes as e
|
episodes as e
|
||||||
where
|
where
|
||||||
e.show_id = "this".id) as episodes_count
|
e.show_id = "this".id) as episodes_count
|
||||||
"""
|
"""
|
||||||
)]
|
)]
|
||||||
public int EpisodesCount { get; set; }
|
public int EpisodesCount { get; set; }
|
||||||
|
|
||||||
|
@ -205,8 +205,7 @@ public abstract record Filter<T> : Filter
|
|||||||
if (type.IsEnum)
|
if (type.IsEnum)
|
||||||
{
|
{
|
||||||
return Parse
|
return Parse
|
||||||
.LetterOrDigit
|
.LetterOrDigit.Many()
|
||||||
.Many()
|
|
||||||
.Text()
|
.Text()
|
||||||
.Then(x =>
|
.Then(x =>
|
||||||
{
|
{
|
||||||
@ -259,14 +258,11 @@ public abstract record Filter<T> : Filter
|
|||||||
}
|
}
|
||||||
|
|
||||||
PropertyInfo? propInfo = types
|
PropertyInfo? propInfo = types
|
||||||
.Select(
|
.Select(x =>
|
||||||
x =>
|
x.GetProperty(
|
||||||
x.GetProperty(
|
prop,
|
||||||
prop,
|
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
|
||||||
BindingFlags.IgnoreCase
|
)
|
||||||
| BindingFlags.Public
|
|
||||||
| BindingFlags.Instance
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
if (propInfo == null)
|
if (propInfo == null)
|
||||||
|
@ -62,17 +62,14 @@ public class Include<T> : Include
|
|||||||
.SelectMany(key =>
|
.SelectMany(key =>
|
||||||
{
|
{
|
||||||
var relations = types
|
var relations = types
|
||||||
.Select(
|
.Select(x =>
|
||||||
x =>
|
x.GetProperty(
|
||||||
x.GetProperty(
|
key,
|
||||||
key,
|
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
|
||||||
BindingFlags.IgnoreCase
|
)!
|
||||||
| BindingFlags.Public
|
|
||||||
| BindingFlags.Instance
|
|
||||||
)!
|
|
||||||
)
|
)
|
||||||
.Select(
|
.Select(prop =>
|
||||||
prop => (prop, attr: prop?.GetCustomAttribute<LoadableRelationAttribute>()!)
|
(prop, attr: prop?.GetCustomAttribute<LoadableRelationAttribute>()!)
|
||||||
)
|
)
|
||||||
.Where(x => x.prop != null && x.attr != null)
|
.Where(x => x.prop != null && x.attr != null)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -120,12 +120,11 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
Type[] types =
|
Type[] types =
|
||||||
typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
|
typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
|
||||||
PropertyInfo? property = types
|
PropertyInfo? property = types
|
||||||
.Select(
|
.Select(x =>
|
||||||
x =>
|
x.GetProperty(
|
||||||
x.GetProperty(
|
key,
|
||||||
key,
|
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
|
||||||
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.FirstOrDefault(x => x != null);
|
.FirstOrDefault(x => x != null);
|
||||||
if (property == null)
|
if (property == null)
|
||||||
|
@ -60,8 +60,8 @@ namespace Kyoo.Utils
|
|||||||
hasChanged = false;
|
hasChanged = false;
|
||||||
if (second == null)
|
if (second == null)
|
||||||
return first;
|
return first;
|
||||||
hasChanged = second.Any(
|
hasChanged = second.Any(x =>
|
||||||
x => !first.ContainsKey(x.Key) || x.Value?.Equals(first[x.Key]) == false
|
!first.ContainsKey(x.Key) || x.Value?.Equals(first[x.Key]) == false
|
||||||
);
|
);
|
||||||
foreach ((T key, T2 value) in first)
|
foreach ((T key, T2 value) in first)
|
||||||
second.TryAdd(key, value);
|
second.TryAdd(key, value);
|
||||||
@ -98,10 +98,9 @@ namespace Kyoo.Utils
|
|||||||
|
|
||||||
Type type = typeof(T);
|
Type type = typeof(T);
|
||||||
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
||||||
.Where(
|
.Where(x =>
|
||||||
x =>
|
x is { CanRead: true, CanWrite: true }
|
||||||
x is { CanRead: true, CanWrite: true }
|
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null
|
||||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (where != null)
|
if (where != null)
|
||||||
|
@ -206,8 +206,8 @@ namespace Kyoo.Utils
|
|||||||
: type.GetInheritanceTree();
|
: type.GetInheritanceTree();
|
||||||
return types
|
return types
|
||||||
.Prepend(type)
|
.Prepend(type)
|
||||||
.FirstOrDefault(
|
.FirstOrDefault(x =>
|
||||||
x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType
|
x.IsGenericType && x.GetGenericTypeDefinition() == genericType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,11 +229,10 @@ namespace Kyoo.Authentication
|
|||||||
private AuthenticateResult _ApiKeyCheck(ActionContext context)
|
private AuthenticateResult _ApiKeyCheck(ActionContext context)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
!context
|
!context.HttpContext.Request.Headers.TryGetValue(
|
||||||
.HttpContext
|
"X-API-Key",
|
||||||
.Request
|
out StringValues apiKey
|
||||||
.Headers
|
)
|
||||||
.TryGetValue("X-API-Key", out StringValues apiKey)
|
|
||||||
)
|
)
|
||||||
return AuthenticateResult.NoResult();
|
return AuthenticateResult.NoResult();
|
||||||
if (!_options.ApiKeys.Contains<string>(apiKey!))
|
if (!_options.ApiKeys.Contains<string>(apiKey!))
|
||||||
@ -262,9 +261,9 @@ namespace Kyoo.Authentication
|
|||||||
|
|
||||||
private async Task<AuthenticateResult> _JwtCheck(ActionContext context)
|
private async Task<AuthenticateResult> _JwtCheck(ActionContext context)
|
||||||
{
|
{
|
||||||
AuthenticateResult ret = await context
|
AuthenticateResult ret = await context.HttpContext.AuthenticateAsync(
|
||||||
.HttpContext
|
JwtBearerDefaults.AuthenticationScheme
|
||||||
.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
|
);
|
||||||
// Change the failure message to make the API nice to use.
|
// Change the failure message to make the API nice to use.
|
||||||
if (ret.Failure != null)
|
if (ret.Failure != null)
|
||||||
return AuthenticateResult.Fail(
|
return AuthenticateResult.Fail(
|
||||||
|
@ -40,10 +40,8 @@ namespace Kyoo.Authentication.Models
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
return Enum.GetNames<Group>()
|
return Enum.GetNames<Group>()
|
||||||
.SelectMany(
|
.SelectMany(group =>
|
||||||
group =>
|
Enum.GetNames<Kind>().Select(kind => $"{group}.{kind}".ToLowerInvariant())
|
||||||
Enum.GetNames<Kind>()
|
|
||||||
.Select(kind => $"{group}.{kind}".ToLowerInvariant())
|
|
||||||
)
|
)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,8 @@ public static class DapperHelper
|
|||||||
.Where(x => !x.Key.StartsWith('_'))
|
.Where(x => !x.Key.StartsWith('_'))
|
||||||
// If first char is lower, assume manual sql instead of reflection.
|
// If first char is lower, assume manual sql instead of reflection.
|
||||||
.Where(x => char.IsLower(key.First()) || x.Value.GetProperty(key) != null)
|
.Where(x => char.IsLower(key.First()) || x.Value.GetProperty(key) != null)
|
||||||
.Select(
|
.Select(x =>
|
||||||
x =>
|
$"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}"
|
||||||
$"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}"
|
|
||||||
)
|
)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
if (keys.Length == 1)
|
if (keys.Length == 1)
|
||||||
@ -149,8 +148,7 @@ public static class DapperHelper
|
|||||||
T Map(T item, IEnumerable<object?> relations)
|
T Map(T item, IEnumerable<object?> relations)
|
||||||
{
|
{
|
||||||
IEnumerable<string> metadatas = include
|
IEnumerable<string> metadatas = include
|
||||||
.Metadatas
|
.Metadatas.Where(x => x is not Include.ProjectedRelation)
|
||||||
.Where(x => x is not Include.ProjectedRelation)
|
|
||||||
.Select(x => x.Name);
|
.Select(x => x.Name);
|
||||||
foreach ((string name, object? value) in metadatas.Zip(relations))
|
foreach ((string name, object? value) in metadatas.Zip(relations))
|
||||||
{
|
{
|
||||||
@ -179,26 +177,24 @@ public static class DapperHelper
|
|||||||
'\n',
|
'\n',
|
||||||
config
|
config
|
||||||
.Skip(1)
|
.Skip(1)
|
||||||
.Select(
|
.Select(x =>
|
||||||
x =>
|
$"when {x.Key}.id is not null then '{x.Value.Name.ToLowerInvariant()}'"
|
||||||
$"when {x.Key}.id is not null then '{x.Value.Name.ToLowerInvariant()}'"
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return $"""
|
return $"""
|
||||||
case
|
case
|
||||||
{cases:raw}
|
{cases:raw}
|
||||||
else '{config.First().Value.Name.ToLowerInvariant():raw}'
|
else '{config.First().Value.Name.ToLowerInvariant():raw}'
|
||||||
end {op}
|
end {op}
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<string> properties = config
|
IEnumerable<string> properties = config
|
||||||
.Where(x => !x.Key.StartsWith('_'))
|
.Where(x => !x.Key.StartsWith('_'))
|
||||||
// If first char is lower, assume manual sql instead of reflection.
|
// If first char is lower, assume manual sql instead of reflection.
|
||||||
.Where(x => char.IsLower(key.First()) || x.Value.GetProperty(key) != null)
|
.Where(x => char.IsLower(key.First()) || x.Value.GetProperty(key) != null)
|
||||||
.Select(
|
.Select(x =>
|
||||||
x =>
|
$"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}"
|
||||||
$"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
FormattableString ret = $"{properties.First():raw} {op}";
|
FormattableString ret = $"{properties.First():raw} {op}";
|
||||||
@ -245,8 +241,7 @@ public static class DapperHelper
|
|||||||
public static string ExpendProjections(Type type, string? prefix, Include include)
|
public static string ExpendProjections(Type type, string? prefix, Include include)
|
||||||
{
|
{
|
||||||
IEnumerable<string> projections = include
|
IEnumerable<string> projections = include
|
||||||
.Metadatas
|
.Metadatas.Select(x => (x as Include.ProjectedRelation)!)
|
||||||
.Select(x => (x as Include.ProjectedRelation)!)
|
|
||||||
.Where(x => x != null)
|
.Where(x => x != null)
|
||||||
.Where(x => type.GetProperty(x.Name) != null)
|
.Where(x => type.GetProperty(x.Name) != null)
|
||||||
.Select(x => x.Sql.Replace("\"this\".", prefix));
|
.Select(x => x.Sql.Replace("\"this\".", prefix));
|
||||||
@ -336,8 +331,8 @@ public static class DapperHelper
|
|||||||
{
|
{
|
||||||
string posterProj = string.Join(
|
string posterProj = string.Join(
|
||||||
", ",
|
", ",
|
||||||
new[] { "poster", "thumbnail", "logo" }.Select(
|
new[] { "poster", "thumbnail", "logo" }.Select(x =>
|
||||||
x => $"{prefix}{x}_source as source, {prefix}{x}_blurhash as blurhash"
|
$"{prefix}{x}_source as source, {prefix}{x}_blurhash as blurhash"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
projection = string.IsNullOrEmpty(projection)
|
projection = string.IsNullOrEmpty(projection)
|
||||||
|
@ -47,12 +47,10 @@ namespace Kyoo.Core.Controllers
|
|||||||
IRepository<Show>.OnEdited += async (show) =>
|
IRepository<Show>.OnEdited += async (show) =>
|
||||||
{
|
{
|
||||||
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
|
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
|
||||||
DatabaseContext database = scope
|
DatabaseContext database =
|
||||||
.ServiceProvider
|
scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
.GetRequiredService<DatabaseContext>();
|
|
||||||
List<Episode> episodes = await database
|
List<Episode> episodes = await database
|
||||||
.Episodes
|
.Episodes.AsTracking()
|
||||||
.AsTracking()
|
|
||||||
.Where(x => x.ShowId == show.Id)
|
.Where(x => x.ShowId == show.Id)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
foreach (Episode ep in episodes)
|
foreach (Episode ep in episodes)
|
||||||
@ -96,19 +94,14 @@ namespace Kyoo.Core.Controllers
|
|||||||
protected override Task<Episode?> GetDuplicated(Episode item)
|
protected override Task<Episode?> GetDuplicated(Episode item)
|
||||||
{
|
{
|
||||||
if (item is { SeasonNumber: not null, EpisodeNumber: not null })
|
if (item is { SeasonNumber: not null, EpisodeNumber: not null })
|
||||||
return _database
|
return _database.Episodes.FirstOrDefaultAsync(x =>
|
||||||
.Episodes
|
x.ShowId == item.ShowId
|
||||||
.FirstOrDefaultAsync(
|
&& x.SeasonNumber == item.SeasonNumber
|
||||||
x =>
|
&& x.EpisodeNumber == item.EpisodeNumber
|
||||||
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
|
|
||||||
);
|
);
|
||||||
|
return _database.Episodes.FirstOrDefaultAsync(x =>
|
||||||
|
x.ShowId == item.ShowId && x.AbsoluteNumber == item.AbsoluteNumber
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -140,11 +133,9 @@ namespace Kyoo.Core.Controllers
|
|||||||
}
|
}
|
||||||
if (resource.SeasonId == null && resource.SeasonNumber != null)
|
if (resource.SeasonId == null && resource.SeasonNumber != null)
|
||||||
{
|
{
|
||||||
resource.Season = await _database
|
resource.Season = await _database.Seasons.FirstOrDefaultAsync(x =>
|
||||||
.Seasons
|
x.ShowId == resource.ShowId && x.SeasonNumber == resource.SeasonNumber
|
||||||
.FirstOrDefaultAsync(
|
);
|
||||||
x => x.ShowId == resource.ShowId && x.SeasonNumber == resource.SeasonNumber
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +143,7 @@ namespace Kyoo.Core.Controllers
|
|||||||
public override async Task Delete(Episode obj)
|
public override async Task Delete(Episode obj)
|
||||||
{
|
{
|
||||||
int epCount = await _database
|
int epCount = await _database
|
||||||
.Episodes
|
.Episodes.Where(x => x.ShowId == obj.ShowId)
|
||||||
.Where(x => x.ShowId == obj.ShowId)
|
|
||||||
.Take(2)
|
.Take(2)
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
_database.Entry(obj).State = EntityState.Deleted;
|
_database.Entry(obj).State = EntityState.Deleted;
|
||||||
|
@ -35,29 +35,29 @@ namespace Kyoo.Core.Controllers
|
|||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
protected override FormattableString Sql =>
|
protected override FormattableString Sql =>
|
||||||
$"""
|
$"""
|
||||||
select
|
|
||||||
s.*, -- Show as s
|
|
||||||
m.*,
|
|
||||||
c.*
|
|
||||||
/* includes */
|
|
||||||
from
|
|
||||||
shows as s
|
|
||||||
full outer join (
|
|
||||||
select
|
select
|
||||||
* -- Movie
|
s.*, -- Show as s
|
||||||
|
m.*,
|
||||||
|
c.*
|
||||||
|
/* includes */
|
||||||
from
|
from
|
||||||
movies) as m on false
|
shows as s
|
||||||
full outer join(
|
full outer join (
|
||||||
select
|
select
|
||||||
c.* -- Collection as c
|
* -- Movie
|
||||||
from
|
from
|
||||||
collections as c
|
movies) as m on false
|
||||||
left join link_collection_show as ls on ls.collection_id = c.id
|
full outer join(
|
||||||
left join link_collection_movie as lm on lm.collection_id = c.id
|
select
|
||||||
group by c.id
|
c.* -- Collection as c
|
||||||
having count(*) > 1
|
from
|
||||||
) as c on false
|
collections as c
|
||||||
""";
|
left join link_collection_show as ls on ls.collection_id = c.id
|
||||||
|
left join link_collection_movie as lm on lm.collection_id = c.id
|
||||||
|
group by c.id
|
||||||
|
having count(*) > 1
|
||||||
|
) as c on false
|
||||||
|
""";
|
||||||
|
|
||||||
protected override Dictionary<string, Type> Config =>
|
protected override Dictionary<string, Type> Config =>
|
||||||
new()
|
new()
|
||||||
|
@ -32,19 +32,19 @@ namespace Kyoo.Core.Controllers
|
|||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
protected override FormattableString Sql =>
|
protected override FormattableString Sql =>
|
||||||
$"""
|
$"""
|
||||||
select
|
|
||||||
e.*, -- Episode as e
|
|
||||||
m.*
|
|
||||||
/* includes */
|
|
||||||
from
|
|
||||||
episodes as e
|
|
||||||
full outer join (
|
|
||||||
select
|
select
|
||||||
* -- Movie
|
e.*, -- Episode as e
|
||||||
|
m.*
|
||||||
|
/* includes */
|
||||||
from
|
from
|
||||||
movies
|
episodes as e
|
||||||
) as m on false
|
full outer join (
|
||||||
""";
|
select
|
||||||
|
* -- Movie
|
||||||
|
from
|
||||||
|
movies
|
||||||
|
) as m on false
|
||||||
|
""";
|
||||||
|
|
||||||
protected override Dictionary<string, Type> Config =>
|
protected override Dictionary<string, Type> Config =>
|
||||||
new() { { "e", typeof(Episode) }, { "m", typeof(Movie) }, };
|
new() { { "e", typeof(Episode) }, { "m", typeof(Movie) }, };
|
||||||
|
@ -47,12 +47,10 @@ namespace Kyoo.Core.Controllers
|
|||||||
IRepository<Show>.OnEdited += async (show) =>
|
IRepository<Show>.OnEdited += async (show) =>
|
||||||
{
|
{
|
||||||
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
|
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
|
||||||
DatabaseContext database = scope
|
DatabaseContext database =
|
||||||
.ServiceProvider
|
scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
.GetRequiredService<DatabaseContext>();
|
|
||||||
List<Season> seasons = await database
|
List<Season> seasons = await database
|
||||||
.Seasons
|
.Seasons.AsTracking()
|
||||||
.AsTracking()
|
|
||||||
.Where(x => x.ShowId == show.Id)
|
.Where(x => x.ShowId == show.Id)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
foreach (Season season in seasons)
|
foreach (Season season in seasons)
|
||||||
@ -77,11 +75,9 @@ namespace Kyoo.Core.Controllers
|
|||||||
|
|
||||||
protected override Task<Season?> GetDuplicated(Season item)
|
protected override Task<Season?> GetDuplicated(Season item)
|
||||||
{
|
{
|
||||||
return _database
|
return _database.Seasons.FirstOrDefaultAsync(x =>
|
||||||
.Seasons
|
x.ShowId == item.ShowId && x.SeasonNumber == item.SeasonNumber
|
||||||
.FirstOrDefaultAsync(
|
);
|
||||||
x => x.ShowId == item.ShowId && x.SeasonNumber == item.SeasonNumber
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -66,11 +66,10 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
{
|
{
|
||||||
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
|
await using AsyncServiceScope scope = CoreModule.Services.CreateAsyncScope();
|
||||||
DatabaseContext db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
DatabaseContext db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
WatchStatusRepository repo = scope
|
WatchStatusRepository repo =
|
||||||
.ServiceProvider
|
scope.ServiceProvider.GetRequiredService<WatchStatusRepository>();
|
||||||
.GetRequiredService<WatchStatusRepository>();
|
List<Guid> users = await db
|
||||||
List<Guid> users = await db.ShowWatchStatus
|
.ShowWatchStatus.IgnoreQueryFilters()
|
||||||
.IgnoreQueryFilters()
|
|
||||||
.Where(x => x.ShowId == ep.ShowId && x.Status == WatchStatus.Completed)
|
.Where(x => x.ShowId == ep.ShowId && x.Status == WatchStatus.Completed)
|
||||||
.Select(x => x.UserId)
|
.Select(x => x.UserId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@ -95,41 +94,41 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
protected FormattableString Sql =>
|
protected FormattableString Sql =>
|
||||||
$"""
|
$"""
|
||||||
select
|
|
||||||
s.*,
|
|
||||||
swe.*, -- Episode as swe
|
|
||||||
m.*
|
|
||||||
/* includes */
|
|
||||||
from (
|
|
||||||
select
|
select
|
||||||
s.*, -- Show as s
|
s.*,
|
||||||
sw.*,
|
swe.*, -- Episode as swe
|
||||||
sw.added_date as order,
|
m.*
|
||||||
sw.status as watch_status
|
/* includes */
|
||||||
from
|
from (
|
||||||
shows as s
|
select
|
||||||
inner join show_watch_status as sw on sw.show_id = s.id
|
s.*, -- Show as s
|
||||||
and sw.user_id = [current_user]) as s
|
sw.*,
|
||||||
full outer join (
|
sw.added_date as order,
|
||||||
select
|
sw.status as watch_status
|
||||||
m.*, -- Movie as m
|
from
|
||||||
mw.*,
|
shows as s
|
||||||
mw.added_date as order,
|
inner join show_watch_status as sw on sw.show_id = s.id
|
||||||
mw.status as watch_status
|
and sw.user_id = [current_user]) as s
|
||||||
from
|
full outer join (
|
||||||
movies as m
|
select
|
||||||
inner join movie_watch_status as mw on mw.movie_id = m.id
|
m.*, -- Movie as m
|
||||||
and mw.user_id = [current_user]) as m on false
|
mw.*,
|
||||||
left join episodes as swe on swe.id = s.next_episode_id
|
mw.added_date as order,
|
||||||
/* includesJoin */
|
mw.status as watch_status
|
||||||
where
|
from
|
||||||
(coalesce(s.watch_status, m.watch_status) = 'watching'::watch_status
|
movies as m
|
||||||
or coalesce(s.watch_status, m.watch_status) = 'planned'::watch_status)
|
inner join movie_watch_status as mw on mw.movie_id = m.id
|
||||||
/* where */
|
and mw.user_id = [current_user]) as m on false
|
||||||
order by
|
left join episodes as swe on swe.id = s.next_episode_id
|
||||||
coalesce(s.order, m.order) desc,
|
/* includesJoin */
|
||||||
coalesce(s.id, m.id) asc
|
where
|
||||||
""";
|
(coalesce(s.watch_status, m.watch_status) = 'watching'::watch_status
|
||||||
|
or coalesce(s.watch_status, m.watch_status) = 'planned'::watch_status)
|
||||||
|
/* where */
|
||||||
|
order by
|
||||||
|
coalesce(s.order, m.order) desc,
|
||||||
|
coalesce(s.id, m.id) asc
|
||||||
|
""";
|
||||||
|
|
||||||
protected Dictionary<string, Type> Config =>
|
protected Dictionary<string, Type> Config =>
|
||||||
new()
|
new()
|
||||||
@ -189,8 +188,7 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
{
|
{
|
||||||
if (include != null)
|
if (include != null)
|
||||||
include.Metadatas = include
|
include.Metadatas = include
|
||||||
.Metadatas
|
.Metadatas.Where(x => x.Name != nameof(Show.WatchStatus))
|
||||||
.Where(x => x.Name != nameof(Show.WatchStatus))
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// We can't use the generic after id hanler since the sort depends on a relation.
|
// We can't use the generic after id hanler since the sort depends on a relation.
|
||||||
@ -226,9 +224,9 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<MovieWatchStatus?> GetMovieStatus(Guid movieId, Guid userId)
|
public Task<MovieWatchStatus?> GetMovieStatus(Guid movieId, Guid userId)
|
||||||
{
|
{
|
||||||
return _database
|
return _database.MovieWatchStatus.FirstOrDefaultAsync(x =>
|
||||||
.MovieWatchStatus
|
x.MovieId == movieId && x.UserId == userId
|
||||||
.FirstOrDefaultAsync(x => x.MovieId == movieId && x.UserId == userId);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -277,8 +275,7 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
||||||
};
|
};
|
||||||
await _database
|
await _database
|
||||||
.MovieWatchStatus
|
.MovieWatchStatus.Upsert(ret)
|
||||||
.Upsert(ret)
|
|
||||||
.UpdateIf(x => status != Watching || x.Status != Completed)
|
.UpdateIf(x => status != Watching || x.Status != Completed)
|
||||||
.RunAsync();
|
.RunAsync();
|
||||||
return ret;
|
return ret;
|
||||||
@ -288,17 +285,16 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
public async Task DeleteMovieStatus(Guid movieId, Guid userId)
|
public async Task DeleteMovieStatus(Guid movieId, Guid userId)
|
||||||
{
|
{
|
||||||
await _database
|
await _database
|
||||||
.MovieWatchStatus
|
.MovieWatchStatus.Where(x => x.MovieId == movieId && x.UserId == userId)
|
||||||
.Where(x => x.MovieId == movieId && x.UserId == userId)
|
|
||||||
.ExecuteDeleteAsync();
|
.ExecuteDeleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<ShowWatchStatus?> GetShowStatus(Guid showId, Guid userId)
|
public Task<ShowWatchStatus?> GetShowStatus(Guid showId, Guid userId)
|
||||||
{
|
{
|
||||||
return _database
|
return _database.ShowWatchStatus.FirstOrDefaultAsync(x =>
|
||||||
.ShowWatchStatus
|
x.ShowId == showId && x.UserId == userId
|
||||||
.FirstOrDefaultAsync(x => x.ShowId == showId && x.UserId == userId);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -315,12 +311,9 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
int unseenEpisodeCount =
|
int unseenEpisodeCount =
|
||||||
status != WatchStatus.Completed
|
status != WatchStatus.Completed
|
||||||
? await _database
|
? await _database
|
||||||
.Episodes
|
.Episodes.Where(x => x.ShowId == showId)
|
||||||
.Where(x => x.ShowId == showId)
|
.Where(x =>
|
||||||
.Where(
|
x.Watched!.First(x => x.UserId == userId)!.Status != WatchStatus.Completed
|
||||||
x =>
|
|
||||||
x.Watched!.First(x => x.UserId == userId)!.Status
|
|
||||||
!= WatchStatus.Completed
|
|
||||||
)
|
)
|
||||||
.CountAsync()
|
.CountAsync()
|
||||||
: 0;
|
: 0;
|
||||||
@ -332,57 +325,47 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
if (status == WatchStatus.Watching)
|
if (status == WatchStatus.Watching)
|
||||||
{
|
{
|
||||||
var cursor = await _database
|
var cursor = await _database
|
||||||
.Episodes
|
.Episodes.IgnoreQueryFilters()
|
||||||
.IgnoreQueryFilters()
|
|
||||||
.Where(x => x.ShowId == showId)
|
.Where(x => x.ShowId == showId)
|
||||||
.OrderByDescending(x => x.AbsoluteNumber)
|
.OrderByDescending(x => x.AbsoluteNumber)
|
||||||
.ThenByDescending(x => x.SeasonNumber)
|
.ThenByDescending(x => x.SeasonNumber)
|
||||||
.ThenByDescending(x => x.EpisodeNumber)
|
.ThenByDescending(x => x.EpisodeNumber)
|
||||||
.Select(
|
.Select(x => new
|
||||||
x =>
|
{
|
||||||
new
|
x.Id,
|
||||||
{
|
x.AbsoluteNumber,
|
||||||
x.Id,
|
x.SeasonNumber,
|
||||||
x.AbsoluteNumber,
|
x.EpisodeNumber,
|
||||||
x.SeasonNumber,
|
Status = x.Watched!.First(x => x.UserId == userId)
|
||||||
x.EpisodeNumber,
|
})
|
||||||
Status = x.Watched!.First(x => x.UserId == userId)
|
.FirstOrDefaultAsync(x =>
|
||||||
}
|
x.Status.Status == WatchStatus.Completed
|
||||||
)
|
|| x.Status.Status == WatchStatus.Watching
|
||||||
.FirstOrDefaultAsync(
|
|
||||||
x =>
|
|
||||||
x.Status.Status == WatchStatus.Completed
|
|
||||||
|| x.Status.Status == WatchStatus.Watching
|
|
||||||
);
|
);
|
||||||
cursorWatchStatus = cursor?.Status;
|
cursorWatchStatus = cursor?.Status;
|
||||||
nextEpisodeId =
|
nextEpisodeId =
|
||||||
cursor?.Status.Status == WatchStatus.Watching
|
cursor?.Status.Status == WatchStatus.Watching
|
||||||
? cursor.Id
|
? cursor.Id
|
||||||
: await _database
|
: await _database
|
||||||
.Episodes
|
.Episodes.IgnoreQueryFilters()
|
||||||
.IgnoreQueryFilters()
|
|
||||||
.Where(x => x.ShowId == showId)
|
.Where(x => x.ShowId == showId)
|
||||||
.OrderBy(x => x.AbsoluteNumber)
|
.OrderBy(x => x.AbsoluteNumber)
|
||||||
.ThenBy(x => x.SeasonNumber)
|
.ThenBy(x => x.SeasonNumber)
|
||||||
.ThenBy(x => x.EpisodeNumber)
|
.ThenBy(x => x.EpisodeNumber)
|
||||||
.Where(
|
.Where(x =>
|
||||||
x =>
|
cursor == null
|
||||||
cursor == null
|
|| x.AbsoluteNumber > cursor.AbsoluteNumber
|
||||||
|| x.AbsoluteNumber > cursor.AbsoluteNumber
|
|| x.SeasonNumber > cursor.SeasonNumber
|
||||||
|| x.SeasonNumber > cursor.SeasonNumber
|
|| (
|
||||||
|| (
|
x.SeasonNumber == cursor.SeasonNumber
|
||||||
x.SeasonNumber == cursor.SeasonNumber
|
&& x.EpisodeNumber > cursor.EpisodeNumber
|
||||||
&& x.EpisodeNumber > cursor.EpisodeNumber
|
)
|
||||||
)
|
|
||||||
)
|
|
||||||
.Select(
|
|
||||||
x =>
|
|
||||||
new
|
|
||||||
{
|
|
||||||
x.Id,
|
|
||||||
Status = x.Watched!.FirstOrDefault(x => x.UserId == userId)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Id,
|
||||||
|
Status = x.Watched!.FirstOrDefault(x => x.UserId == userId)
|
||||||
|
})
|
||||||
.Where(x => x.Status == null || x.Status.Status != WatchStatus.Completed)
|
.Where(x => x.Status == null || x.Status.Status != WatchStatus.Completed)
|
||||||
// The as Guid? is here to add the nullability status of the queryable.
|
// The as Guid? is here to add the nullability status of the queryable.
|
||||||
// Without this, FirstOrDefault returns new Guid() when no result is found (which is 16 0s and invalid in sql).
|
// Without this, FirstOrDefault returns new Guid() when no result is found (which is 16 0s and invalid in sql).
|
||||||
@ -392,24 +375,19 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
else if (status == WatchStatus.Completed)
|
else if (status == WatchStatus.Completed)
|
||||||
{
|
{
|
||||||
List<Guid> episodes = await _database
|
List<Guid> episodes = await _database
|
||||||
.Episodes
|
.Episodes.Where(x => x.ShowId == showId)
|
||||||
.Where(x => x.ShowId == showId)
|
|
||||||
.Select(x => x.Id)
|
.Select(x => x.Id)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
await _database
|
await _database
|
||||||
.EpisodeWatchStatus
|
.EpisodeWatchStatus.UpsertRange(
|
||||||
.UpsertRange(
|
episodes.Select(episodeId => new EpisodeWatchStatus
|
||||||
episodes.Select(
|
{
|
||||||
episodeId =>
|
UserId = userId,
|
||||||
new EpisodeWatchStatus
|
EpisodeId = episodeId,
|
||||||
{
|
Status = WatchStatus.Completed,
|
||||||
UserId = userId,
|
AddedDate = DateTime.UtcNow,
|
||||||
EpisodeId = episodeId,
|
PlayedDate = DateTime.UtcNow
|
||||||
Status = WatchStatus.Completed,
|
})
|
||||||
AddedDate = DateTime.UtcNow,
|
|
||||||
PlayedDate = DateTime.UtcNow
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.UpdateIf(x => x.Status == Watching || x.Status == Planned)
|
.UpdateIf(x => x.Status == Watching || x.Status == Planned)
|
||||||
.RunAsync();
|
.RunAsync();
|
||||||
@ -435,8 +413,7 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
||||||
};
|
};
|
||||||
await _database
|
await _database
|
||||||
.ShowWatchStatus
|
.ShowWatchStatus.Upsert(ret)
|
||||||
.Upsert(ret)
|
|
||||||
.UpdateIf(x => status != Watching || x.Status != Completed || newEpisode)
|
.UpdateIf(x => status != Watching || x.Status != Completed || newEpisode)
|
||||||
.RunAsync();
|
.RunAsync();
|
||||||
return ret;
|
return ret;
|
||||||
@ -446,22 +423,20 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
public async Task DeleteShowStatus(Guid showId, Guid userId)
|
public async Task DeleteShowStatus(Guid showId, Guid userId)
|
||||||
{
|
{
|
||||||
await _database
|
await _database
|
||||||
.ShowWatchStatus
|
.ShowWatchStatus.IgnoreAutoIncludes()
|
||||||
.IgnoreAutoIncludes()
|
|
||||||
.Where(x => x.ShowId == showId && x.UserId == userId)
|
.Where(x => x.ShowId == showId && x.UserId == userId)
|
||||||
.ExecuteDeleteAsync();
|
.ExecuteDeleteAsync();
|
||||||
await _database
|
await _database
|
||||||
.EpisodeWatchStatus
|
.EpisodeWatchStatus.Where(x => x.Episode.ShowId == showId && x.UserId == userId)
|
||||||
.Where(x => x.Episode.ShowId == showId && x.UserId == userId)
|
|
||||||
.ExecuteDeleteAsync();
|
.ExecuteDeleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<EpisodeWatchStatus?> GetEpisodeStatus(Guid episodeId, Guid userId)
|
public Task<EpisodeWatchStatus?> GetEpisodeStatus(Guid episodeId, Guid userId)
|
||||||
{
|
{
|
||||||
return _database
|
return _database.EpisodeWatchStatus.FirstOrDefaultAsync(x =>
|
||||||
.EpisodeWatchStatus
|
x.EpisodeId == episodeId && x.UserId == userId
|
||||||
.FirstOrDefaultAsync(x => x.EpisodeId == episodeId && x.UserId == userId);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -510,8 +485,7 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
||||||
};
|
};
|
||||||
await _database
|
await _database
|
||||||
.EpisodeWatchStatus
|
.EpisodeWatchStatus.Upsert(ret)
|
||||||
.Upsert(ret)
|
|
||||||
.UpdateIf(x => status != Watching || x.Status != Completed)
|
.UpdateIf(x => status != Watching || x.Status != Completed)
|
||||||
.RunAsync();
|
.RunAsync();
|
||||||
await SetShowStatus(episode.ShowId, userId, WatchStatus.Watching);
|
await SetShowStatus(episode.ShowId, userId, WatchStatus.Watching);
|
||||||
@ -522,8 +496,7 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
public async Task DeleteEpisodeStatus(Guid episodeId, Guid userId)
|
public async Task DeleteEpisodeStatus(Guid episodeId, Guid userId)
|
||||||
{
|
{
|
||||||
await _database
|
await _database
|
||||||
.EpisodeWatchStatus
|
.EpisodeWatchStatus.Where(x => x.EpisodeId == episodeId && x.UserId == userId)
|
||||||
.Where(x => x.EpisodeId == episodeId && x.UserId == userId)
|
|
||||||
.ExecuteDeleteAsync();
|
.ExecuteDeleteAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,14 +212,13 @@ namespace Kyoo.Core.Controllers
|
|||||||
{
|
{
|
||||||
IEnumerable<string> images = new[] { "poster", "thumbnail", "logo" }
|
IEnumerable<string> images = new[] { "poster", "thumbnail", "logo" }
|
||||||
.SelectMany(x => _GetBaseImagePath(item, x))
|
.SelectMany(x => _GetBaseImagePath(item, x))
|
||||||
.SelectMany(
|
.SelectMany(x =>
|
||||||
x =>
|
new[]
|
||||||
new[]
|
{
|
||||||
{
|
ImageQuality.High.ToString().ToLowerInvariant(),
|
||||||
ImageQuality.High.ToString().ToLowerInvariant(),
|
ImageQuality.Medium.ToString().ToLowerInvariant(),
|
||||||
ImageQuality.Medium.ToString().ToLowerInvariant(),
|
ImageQuality.Low.ToString().ToLowerInvariant(),
|
||||||
ImageQuality.Low.ToString().ToLowerInvariant(),
|
}.Select(quality => $"{x}.{quality}.webp")
|
||||||
}.Select(quality => $"{x}.{quality}.webp")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach (string image in images)
|
foreach (string image in images)
|
||||||
|
@ -105,8 +105,8 @@ namespace Kyoo.Core
|
|||||||
options.SuppressMapClientErrors = true;
|
options.SuppressMapClientErrors = true;
|
||||||
options.InvalidModelStateResponseFactory = ctx =>
|
options.InvalidModelStateResponseFactory = ctx =>
|
||||||
{
|
{
|
||||||
string[] errors = ctx.ModelState
|
string[] errors = ctx
|
||||||
.SelectMany(x => x.Value!.Errors)
|
.ModelState.SelectMany(x => x.Value!.Errors)
|
||||||
.Select(x => x.ErrorMessage)
|
.Select(x => x.ErrorMessage)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return new BadRequestObjectResult(new RequestError(errors));
|
return new BadRequestObjectResult(new RequestError(errors));
|
||||||
|
@ -45,13 +45,11 @@ namespace Kyoo.Core.Api
|
|||||||
protected Page<TResult> Page<TResult>(ICollection<TResult> resources, int limit)
|
protected Page<TResult> Page<TResult>(ICollection<TResult> resources, int limit)
|
||||||
where TResult : IResource
|
where TResult : IResource
|
||||||
{
|
{
|
||||||
Dictionary<string, string> query = Request
|
Dictionary<string, string> query = Request.Query.ToDictionary(
|
||||||
.Query
|
x => x.Key,
|
||||||
.ToDictionary(
|
x => x.Value.ToString(),
|
||||||
x => x.Key,
|
StringComparer.InvariantCultureIgnoreCase
|
||||||
x => x.Value.ToString(),
|
);
|
||||||
StringComparer.InvariantCultureIgnoreCase
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the query was sorted randomly, add the seed to the url to get reproducible links (next,prev,first...)
|
// If the query was sorted randomly, add the seed to the url to get reproducible links (next,prev,first...)
|
||||||
if (query.ContainsKey("sortBy"))
|
if (query.ContainsKey("sortBy"))
|
||||||
@ -66,13 +64,11 @@ namespace Kyoo.Core.Api
|
|||||||
protected SearchPage<TResult> SearchPage<TResult>(SearchPage<TResult>.SearchResult result)
|
protected SearchPage<TResult> SearchPage<TResult>(SearchPage<TResult>.SearchResult result)
|
||||||
where TResult : IResource
|
where TResult : IResource
|
||||||
{
|
{
|
||||||
Dictionary<string, string> query = Request
|
Dictionary<string, string> query = Request.Query.ToDictionary(
|
||||||
.Query
|
x => x.Key,
|
||||||
.ToDictionary(
|
x => x.Value.ToString(),
|
||||||
x => x.Key,
|
StringComparer.InvariantCultureIgnoreCase
|
||||||
x => x.Value.ToString(),
|
);
|
||||||
StringComparer.InvariantCultureIgnoreCase
|
|
||||||
);
|
|
||||||
|
|
||||||
string self = Request.Path + query.ToQueryString();
|
string self = Request.Path + query.ToQueryString();
|
||||||
string? previous = null;
|
string? previous = null;
|
||||||
|
@ -28,14 +28,13 @@ public class FilterBinder : IModelBinder
|
|||||||
{
|
{
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ValueProviderResult fields = bindingContext
|
ValueProviderResult fields = bindingContext.ValueProvider.GetValue(
|
||||||
.ValueProvider
|
bindingContext.FieldName
|
||||||
.GetValue(bindingContext.FieldName);
|
);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
object? filter = bindingContext
|
object? filter = bindingContext
|
||||||
.ModelType
|
.ModelType.GetMethod(nameof(Filter<object>.From))!
|
||||||
.GetMethod(nameof(Filter<object>.From))!
|
|
||||||
.Invoke(null, new object?[] { fields.FirstValue });
|
.Invoke(null, new object?[] { fields.FirstValue });
|
||||||
bindingContext.Result = ModelBindingResult.Success(filter);
|
bindingContext.Result = ModelBindingResult.Success(filter);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -31,14 +31,13 @@ public class IncludeBinder : IModelBinder
|
|||||||
|
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ValueProviderResult fields = bindingContext
|
ValueProviderResult fields = bindingContext.ValueProvider.GetValue(
|
||||||
.ValueProvider
|
bindingContext.FieldName
|
||||||
.GetValue(bindingContext.FieldName);
|
);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
object include = bindingContext
|
object include = bindingContext
|
||||||
.ModelType
|
.ModelType.GetMethod(nameof(Include<object>.From))!
|
||||||
.GetMethod(nameof(Include<object>.From))!
|
|
||||||
.Invoke(null, new object?[] { fields.FirstValue })!;
|
.Invoke(null, new object?[] { fields.FirstValue })!;
|
||||||
bindingContext.Result = ModelBindingResult.Success(include);
|
bindingContext.Result = ModelBindingResult.Success(include);
|
||||||
bindingContext.HttpContext.Items["fields"] = ((dynamic)include).Fields;
|
bindingContext.HttpContext.Items["fields"] = ((dynamic)include).Fields;
|
||||||
|
@ -32,9 +32,9 @@ public class SortBinder : IModelBinder
|
|||||||
|
|
||||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
{
|
{
|
||||||
ValueProviderResult sortBy = bindingContext
|
ValueProviderResult sortBy = bindingContext.ValueProvider.GetValue(
|
||||||
.ValueProvider
|
bindingContext.FieldName
|
||||||
.GetValue(bindingContext.FieldName);
|
);
|
||||||
uint seed = BitConverter.ToUInt32(
|
uint seed = BitConverter.ToUInt32(
|
||||||
BitConverter.GetBytes(_rng.Next(int.MinValue, int.MaxValue)),
|
BitConverter.GetBytes(_rng.Next(int.MinValue, int.MaxValue)),
|
||||||
0
|
0
|
||||||
@ -42,8 +42,7 @@ public class SortBinder : IModelBinder
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
object sort = bindingContext
|
object sort = bindingContext
|
||||||
.ModelType
|
.ModelType.GetMethod(nameof(Sort<Movie>.From))!
|
||||||
.GetMethod(nameof(Sort<Movie>.From))!
|
|
||||||
.Invoke(null, new object?[] { sortBy.FirstValue, seed })!;
|
.Invoke(null, new object?[] { sortBy.FirstValue, seed })!;
|
||||||
bindingContext.Result = ModelBindingResult.Success(sort);
|
bindingContext.Result = ModelBindingResult.Success(sort);
|
||||||
bindingContext.HttpContext.Items["seed"] = seed;
|
bindingContext.HttpContext.Items["seed"] = seed;
|
||||||
|
@ -85,17 +85,12 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Show> fields
|
[FromQuery] Include<Show> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Show> resources = await _libraryManager
|
ICollection<Show> resources = await _libraryManager.Shows.GetAll(
|
||||||
.Shows
|
Filter.And(filter, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio!.Slug)),
|
||||||
.GetAll(
|
sortBy,
|
||||||
Filter.And(
|
fields,
|
||||||
filter,
|
pagination
|
||||||
identifier.Matcher<Show>(x => x.StudioId, x => x.Studio!.Slug)
|
);
|
||||||
),
|
|
||||||
sortBy,
|
|
||||||
fields,
|
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
|
@ -200,17 +200,12 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Show>? fields
|
[FromQuery] Include<Show>? fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Show> resources = await _libraryManager
|
ICollection<Show> resources = await _libraryManager.Shows.GetAll(
|
||||||
.Shows
|
Filter.And(filter, identifier.IsContainedIn<Show, Collection>(x => x.Collections)),
|
||||||
.GetAll(
|
sortBy == new Sort<Show>.Default() ? new Sort<Show>.By(x => x.AirDate) : sortBy,
|
||||||
Filter.And(
|
fields,
|
||||||
filter,
|
pagination
|
||||||
identifier.IsContainedIn<Show, Collection>(x => x.Collections)
|
);
|
||||||
),
|
|
||||||
sortBy == new Sort<Show>.Default() ? new Sort<Show>.By(x => x.AirDate) : sortBy,
|
|
||||||
fields,
|
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
@ -249,19 +244,12 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Movie>? fields
|
[FromQuery] Include<Movie>? fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Movie> resources = await _libraryManager
|
ICollection<Movie> resources = await _libraryManager.Movies.GetAll(
|
||||||
.Movies
|
Filter.And(filter, identifier.IsContainedIn<Movie, Collection>(x => x.Collections)),
|
||||||
.GetAll(
|
sortBy == new Sort<Movie>.Default() ? new Sort<Movie>.By(x => x.AirDate) : sortBy,
|
||||||
Filter.And(
|
fields,
|
||||||
filter,
|
pagination
|
||||||
identifier.IsContainedIn<Movie, Collection>(x => x.Collections)
|
);
|
||||||
),
|
|
||||||
sortBy == new Sort<Movie>.Default()
|
|
||||||
? new Sort<Movie>.By(x => x.AirDate)
|
|
||||||
: sortBy,
|
|
||||||
fields,
|
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
|
@ -77,9 +77,10 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Show> fields
|
[FromQuery] Include<Show> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return await _libraryManager
|
return await _libraryManager.Shows.Get(
|
||||||
.Shows
|
identifier.IsContainedIn<Show, Episode>(x => x.Episodes!),
|
||||||
.Get(identifier.IsContainedIn<Show, Episode>(x => x.Episodes!), fields);
|
fields
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -103,9 +104,10 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Season> fields
|
[FromQuery] Include<Season> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Season? ret = await _libraryManager
|
Season? ret = await _libraryManager.Seasons.GetOrDefault(
|
||||||
.Seasons
|
identifier.IsContainedIn<Season, Episode>(x => x.Episodes!),
|
||||||
.GetOrDefault(identifier.IsContainedIn<Season, Episode>(x => x.Episodes!), fields);
|
fields
|
||||||
|
);
|
||||||
if (ret != null)
|
if (ret != null)
|
||||||
return ret;
|
return ret;
|
||||||
Episode? episode = await identifier.Match(
|
Episode? episode = await identifier.Match(
|
||||||
@ -170,9 +172,13 @@ namespace Kyoo.Core.Api
|
|||||||
id => Task.FromResult(id),
|
id => Task.FromResult(id),
|
||||||
async slug => (await _libraryManager.Episodes.Get(slug)).Id
|
async slug => (await _libraryManager.Episodes.Get(slug)).Id
|
||||||
);
|
);
|
||||||
return await _libraryManager
|
return await _libraryManager.WatchStatus.SetEpisodeStatus(
|
||||||
.WatchStatus
|
id,
|
||||||
.SetEpisodeStatus(id, User.GetIdOrThrow(), status, watchedTime, percent);
|
User.GetIdOrThrow(),
|
||||||
|
status,
|
||||||
|
watchedTime,
|
||||||
|
percent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -113,9 +113,10 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Studio> fields
|
[FromQuery] Include<Studio> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return await _libraryManager
|
return await _libraryManager.Studios.Get(
|
||||||
.Studios
|
identifier.IsContainedIn<Studio, Movie>(x => x.Movies!),
|
||||||
.Get(identifier.IsContainedIn<Studio, Movie>(x => x.Movies!), fields);
|
fields
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -146,14 +147,12 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Collection> fields
|
[FromQuery] Include<Collection> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Collection> resources = await _libraryManager
|
ICollection<Collection> resources = await _libraryManager.Collections.GetAll(
|
||||||
.Collections
|
Filter.And(filter, identifier.IsContainedIn<Collection, Movie>(x => x.Movies)),
|
||||||
.GetAll(
|
sortBy,
|
||||||
Filter.And(filter, identifier.IsContainedIn<Collection, Movie>(x => x.Movies)),
|
fields,
|
||||||
sortBy,
|
pagination
|
||||||
fields,
|
);
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
@ -219,9 +218,13 @@ namespace Kyoo.Core.Api
|
|||||||
id => Task.FromResult(id),
|
id => Task.FromResult(id),
|
||||||
async slug => (await _libraryManager.Movies.Get(slug)).Id
|
async slug => (await _libraryManager.Movies.Get(slug)).Id
|
||||||
);
|
);
|
||||||
return await _libraryManager
|
return await _libraryManager.WatchStatus.SetMovieStatus(
|
||||||
.WatchStatus
|
id,
|
||||||
.SetMovieStatus(id, User.GetIdOrThrow(), status, watchedTime, percent);
|
User.GetIdOrThrow(),
|
||||||
|
status,
|
||||||
|
watchedTime,
|
||||||
|
percent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -86,17 +86,15 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Episode> fields
|
[FromQuery] Include<Episode> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Episode> resources = await _libraryManager
|
ICollection<Episode> resources = await _libraryManager.Episodes.GetAll(
|
||||||
.Episodes
|
Filter.And(
|
||||||
.GetAll(
|
filter,
|
||||||
Filter.And(
|
identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season!.Slug)
|
||||||
filter,
|
),
|
||||||
identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season!.Slug)
|
sortBy,
|
||||||
),
|
fields,
|
||||||
sortBy,
|
pagination
|
||||||
fields,
|
);
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
@ -125,9 +123,10 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Show> fields
|
[FromQuery] Include<Show> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Show? ret = await _libraryManager
|
Show? ret = await _libraryManager.Shows.GetOrDefault(
|
||||||
.Shows
|
identifier.IsContainedIn<Show, Season>(x => x.Seasons!),
|
||||||
.GetOrDefault(identifier.IsContainedIn<Show, Season>(x => x.Seasons!), fields);
|
fields
|
||||||
|
);
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -88,17 +88,12 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Season> fields
|
[FromQuery] Include<Season> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Season> resources = await _libraryManager
|
ICollection<Season> resources = await _libraryManager.Seasons.GetAll(
|
||||||
.Seasons
|
Filter.And(filter, identifier.Matcher<Season>(x => x.ShowId, x => x.Show!.Slug)),
|
||||||
.GetAll(
|
sortBy,
|
||||||
Filter.And(
|
fields,
|
||||||
filter,
|
pagination
|
||||||
identifier.Matcher<Season>(x => x.ShowId, x => x.Show!.Slug)
|
);
|
||||||
),
|
|
||||||
sortBy,
|
|
||||||
fields,
|
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
@ -136,17 +131,12 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Episode> fields
|
[FromQuery] Include<Episode> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Episode> resources = await _libraryManager
|
ICollection<Episode> resources = await _libraryManager.Episodes.GetAll(
|
||||||
.Episodes
|
Filter.And(filter, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show!.Slug)),
|
||||||
.GetAll(
|
sortBy,
|
||||||
Filter.And(
|
fields,
|
||||||
filter,
|
pagination
|
||||||
identifier.Matcher<Episode>(x => x.ShowId, x => x.Show!.Slug)
|
);
|
||||||
),
|
|
||||||
sortBy,
|
|
||||||
fields,
|
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
@ -210,9 +200,10 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Studio> fields
|
[FromQuery] Include<Studio> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return await _libraryManager
|
return await _libraryManager.Studios.Get(
|
||||||
.Studios
|
identifier.IsContainedIn<Studio, Show>(x => x.Shows!),
|
||||||
.Get(identifier.IsContainedIn<Studio, Show>(x => x.Shows!), fields);
|
fields
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -243,14 +234,12 @@ namespace Kyoo.Core.Api
|
|||||||
[FromQuery] Include<Collection> fields
|
[FromQuery] Include<Collection> fields
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ICollection<Collection> resources = await _libraryManager
|
ICollection<Collection> resources = await _libraryManager.Collections.GetAll(
|
||||||
.Collections
|
Filter.And(filter, identifier.IsContainedIn<Collection, Show>(x => x.Shows!)),
|
||||||
.GetAll(
|
sortBy,
|
||||||
Filter.And(filter, identifier.IsContainedIn<Collection, Show>(x => x.Shows!)),
|
fields,
|
||||||
sortBy,
|
pagination
|
||||||
fields,
|
);
|
||||||
pagination
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!resources.Any()
|
!resources.Any()
|
||||||
|
@ -35,14 +35,13 @@ namespace Kyoo.Core.Api
|
|||||||
public class ProxyApi : Controller
|
public class ProxyApi : Controller
|
||||||
{
|
{
|
||||||
private readonly HttpProxyOptions _proxyOptions = HttpProxyOptionsBuilder
|
private readonly HttpProxyOptions _proxyOptions = HttpProxyOptionsBuilder
|
||||||
.Instance
|
.Instance.WithHandleFailure(
|
||||||
.WithHandleFailure(
|
|
||||||
async (context, exception) =>
|
async (context, exception) =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||||
await context
|
await context.Response.WriteAsJsonAsync(
|
||||||
.Response
|
new RequestError("Service unavailable")
|
||||||
.WriteAsJsonAsync(new RequestError("Service unavailable"));
|
);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.Build();
|
.Build();
|
||||||
|
@ -150,25 +150,19 @@ namespace Kyoo.Host
|
|||||||
.ConfigureAppConfiguration(x => _SetupConfig(x, args))
|
.ConfigureAppConfiguration(x => _SetupConfig(x, args))
|
||||||
.UseSerilog((host, services, builder) => _ConfigureLogging(builder))
|
.UseSerilog((host, services, builder) => _ConfigureLogging(builder))
|
||||||
.ConfigureServices(x => x.AddRouting())
|
.ConfigureServices(x => x.AddRouting())
|
||||||
.ConfigureWebHost(
|
.ConfigureWebHost(x =>
|
||||||
x =>
|
x.UseKestrel(options =>
|
||||||
x.UseKestrel(options =>
|
{
|
||||||
{
|
options.AddServerHeader = false;
|
||||||
options.AddServerHeader = false;
|
})
|
||||||
})
|
.UseIIS()
|
||||||
.UseIIS()
|
.UseIISIntegration()
|
||||||
.UseIISIntegration()
|
.UseUrls(
|
||||||
.UseUrls(
|
Environment.GetEnvironmentVariable("KYOO_BIND_URL") ?? "http://*:5000"
|
||||||
Environment.GetEnvironmentVariable("KYOO_BIND_URL")
|
)
|
||||||
?? "http://*:5000"
|
.UseStartup(host =>
|
||||||
)
|
PluginsStartup.FromWebHost(host, new LoggerFactory().AddSerilog())
|
||||||
.UseStartup(
|
)
|
||||||
host =>
|
|
||||||
PluginsStartup.FromWebHost(
|
|
||||||
host,
|
|
||||||
new LoggerFactory().AddSerilog()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,20 +190,13 @@ namespace Kyoo.Host
|
|||||||
"[{@t:HH:mm:ss} {@l:u3} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1), 25} "
|
"[{@t:HH:mm:ss} {@l:u3} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1), 25} "
|
||||||
+ "({@i:D10})] {@m}{#if not EndsWith(@m, '\n')}\n{#end}{@x}";
|
+ "({@i:D10})] {@m}{#if not EndsWith(@m, '\n')}\n{#end}{@x}";
|
||||||
builder
|
builder
|
||||||
.MinimumLevel
|
.MinimumLevel.Warning()
|
||||||
.Warning()
|
.MinimumLevel.Override("Kyoo", LogEventLevel.Verbose)
|
||||||
.MinimumLevel
|
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Verbose)
|
||||||
.Override("Kyoo", LogEventLevel.Verbose)
|
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Fatal)
|
||||||
.MinimumLevel
|
.WriteTo.Console(new ExpressionTemplate(template, theme: TemplateTheme.Code))
|
||||||
.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Verbose)
|
.Enrich.WithThreadId()
|
||||||
.MinimumLevel
|
.Enrich.FromLogContext();
|
||||||
.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Fatal)
|
|
||||||
.WriteTo
|
|
||||||
.Console(new ExpressionTemplate(template, theme: TemplateTheme.Code))
|
|
||||||
.Enrich
|
|
||||||
.WithThreadId()
|
|
||||||
.Enrich
|
|
||||||
.FromLogContext();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,8 +127,8 @@ namespace Kyoo.Host
|
|||||||
.SelectMany(x => x.ConfigureSteps)
|
.SelectMany(x => x.ConfigureSteps)
|
||||||
.OrderByDescending(x => x.Priority);
|
.OrderByDescending(x => x.Priority);
|
||||||
|
|
||||||
using ILifetimeScope scope = container.BeginLifetimeScope(
|
using ILifetimeScope scope = container.BeginLifetimeScope(x =>
|
||||||
x => x.RegisterInstance(app).SingleInstance().ExternallyOwned()
|
x.RegisterInstance(app).SingleInstance().ExternallyOwned()
|
||||||
);
|
);
|
||||||
IServiceProvider provider = scope.Resolve<IServiceProvider>();
|
IServiceProvider provider = scope.Resolve<IServiceProvider>();
|
||||||
foreach (IStartupAction step in steps)
|
foreach (IStartupAction step in steps)
|
||||||
|
@ -39,8 +39,10 @@ public class SearchManager : ISearchManager
|
|||||||
Sort<T>.By @sortBy
|
Sort<T>.By @sortBy
|
||||||
=> MeilisearchModule
|
=> MeilisearchModule
|
||||||
.IndexSettings[index]
|
.IndexSettings[index]
|
||||||
.SortableAttributes
|
.SortableAttributes.Contains(
|
||||||
.Contains(sortBy.Key, StringComparer.InvariantCultureIgnoreCase)
|
sortBy.Key,
|
||||||
|
StringComparer.InvariantCultureIgnoreCase
|
||||||
|
)
|
||||||
? new[]
|
? new[]
|
||||||
{
|
{
|
||||||
$"{CamelCase.ConvertName(sortBy.Key)}:{(sortBy.Desendant ? "desc" : "asc")}"
|
$"{CamelCase.ConvertName(sortBy.Key)}:{(sortBy.Desendant ? "desc" : "asc")}"
|
||||||
|
@ -369,16 +369,13 @@ namespace Kyoo.Postgresql
|
|||||||
modelBuilder.Entity<Season>().HasIndex(x => x.Slug).IsUnique();
|
modelBuilder.Entity<Season>().HasIndex(x => x.Slug).IsUnique();
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.Entity<Episode>()
|
.Entity<Episode>()
|
||||||
.HasIndex(
|
.HasIndex(x => new
|
||||||
x =>
|
{
|
||||||
new
|
ShowID = x.ShowId,
|
||||||
{
|
x.SeasonNumber,
|
||||||
ShowID = x.ShowId,
|
x.EpisodeNumber,
|
||||||
x.SeasonNumber,
|
x.AbsoluteNumber
|
||||||
x.EpisodeNumber,
|
})
|
||||||
x.AbsoluteNumber
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
modelBuilder.Entity<Episode>().HasIndex(x => x.Slug).IsUnique();
|
modelBuilder.Entity<Episode>().HasIndex(x => x.Slug).IsUnique();
|
||||||
modelBuilder.Entity<User>().HasIndex(x => x.Slug).IsUnique();
|
modelBuilder.Entity<User>().HasIndex(x => x.Slug).IsUnique();
|
||||||
|
@ -41,42 +41,41 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "collections",
|
name: "collections",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
slug = table.Column<string>(
|
||||||
slug = table.Column<string>(
|
type: "character varying(256)",
|
||||||
type: "character varying(256)",
|
maxLength: 256,
|
||||||
maxLength: 256,
|
nullable: false
|
||||||
nullable: false
|
),
|
||||||
),
|
name = table.Column<string>(type: "text", nullable: false),
|
||||||
name = table.Column<string>(type: "text", nullable: false),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
poster_blurhash = table.Column<string>(
|
||||||
poster_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
thumbnail_blurhash = table.Column<string>(
|
||||||
thumbnail_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
logo_blurhash = table.Column<string>(
|
||||||
logo_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
external_id = table.Column<string>(type: "json", nullable: false)
|
||||||
external_id = table.Column<string>(type: "json", nullable: false)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_collections", x => x.id);
|
table.PrimaryKey("pk_collections", x => x.id);
|
||||||
@ -85,18 +84,17 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "studios",
|
name: "studios",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
slug = table.Column<string>(
|
||||||
slug = table.Column<string>(
|
type: "character varying(256)",
|
||||||
type: "character varying(256)",
|
maxLength: 256,
|
||||||
maxLength: 256,
|
nullable: false
|
||||||
nullable: false
|
),
|
||||||
),
|
name = table.Column<string>(type: "text", nullable: false),
|
||||||
name = table.Column<string>(type: "text", nullable: false),
|
external_id = table.Column<string>(type: "json", nullable: false)
|
||||||
external_id = table.Column<string>(type: "json", nullable: false)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_studios", x => x.id);
|
table.PrimaryKey("pk_studios", x => x.id);
|
||||||
@ -105,31 +103,30 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "users",
|
name: "users",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
slug = table.Column<string>(
|
||||||
slug = table.Column<string>(
|
type: "character varying(256)",
|
||||||
type: "character varying(256)",
|
maxLength: 256,
|
||||||
maxLength: 256,
|
nullable: false
|
||||||
nullable: false
|
),
|
||||||
),
|
username = table.Column<string>(type: "text", nullable: false),
|
||||||
username = table.Column<string>(type: "text", nullable: false),
|
email = table.Column<string>(type: "text", nullable: false),
|
||||||
email = table.Column<string>(type: "text", nullable: false),
|
password = table.Column<string>(type: "text", nullable: false),
|
||||||
password = table.Column<string>(type: "text", nullable: false),
|
permissions = table.Column<string[]>(type: "text[]", nullable: false),
|
||||||
permissions = table.Column<string[]>(type: "text[]", nullable: false),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
logo_blurhash = table.Column<string>(
|
||||||
logo_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
)
|
||||||
)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_users", x => x.id);
|
table.PrimaryKey("pk_users", x => x.id);
|
||||||
@ -138,56 +135,55 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "movies",
|
name: "movies",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
slug = table.Column<string>(
|
||||||
slug = table.Column<string>(
|
type: "character varying(256)",
|
||||||
type: "character varying(256)",
|
maxLength: 256,
|
||||||
maxLength: 256,
|
nullable: false
|
||||||
nullable: false
|
),
|
||||||
),
|
name = table.Column<string>(type: "text", nullable: false),
|
||||||
name = table.Column<string>(type: "text", nullable: false),
|
tagline = table.Column<string>(type: "text", nullable: true),
|
||||||
tagline = table.Column<string>(type: "text", nullable: true),
|
aliases = table.Column<string[]>(type: "text[]", nullable: false),
|
||||||
aliases = table.Column<string[]>(type: "text[]", nullable: false),
|
path = table.Column<string>(type: "text", nullable: false),
|
||||||
path = table.Column<string>(type: "text", nullable: false),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true),
|
tags = table.Column<string[]>(type: "text[]", nullable: false),
|
||||||
tags = table.Column<string[]>(type: "text[]", nullable: false),
|
genres = table.Column<Genre[]>(type: "genre[]", nullable: false),
|
||||||
genres = table.Column<Genre[]>(type: "genre[]", nullable: false),
|
status = table.Column<Status>(type: "status", nullable: false),
|
||||||
status = table.Column<Status>(type: "status", nullable: false),
|
rating = table.Column<int>(type: "integer", nullable: false),
|
||||||
rating = table.Column<int>(type: "integer", nullable: false),
|
runtime = table.Column<int>(type: "integer", nullable: false),
|
||||||
runtime = table.Column<int>(type: "integer", nullable: false),
|
air_date = table.Column<DateTime>(
|
||||||
air_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
poster_blurhash = table.Column<string>(
|
||||||
poster_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
thumbnail_blurhash = table.Column<string>(
|
||||||
thumbnail_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
logo_blurhash = table.Column<string>(
|
||||||
logo_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
trailer = table.Column<string>(type: "text", nullable: true),
|
||||||
trailer = table.Column<string>(type: "text", nullable: true),
|
external_id = table.Column<string>(type: "json", nullable: false),
|
||||||
external_id = table.Column<string>(type: "json", nullable: false),
|
studio_id = table.Column<Guid>(type: "uuid", nullable: true)
|
||||||
studio_id = table.Column<Guid>(type: "uuid", nullable: true)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_movies", x => x.id);
|
table.PrimaryKey("pk_movies", x => x.id);
|
||||||
@ -203,58 +199,57 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "shows",
|
name: "shows",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
slug = table.Column<string>(
|
||||||
slug = table.Column<string>(
|
type: "character varying(256)",
|
||||||
type: "character varying(256)",
|
maxLength: 256,
|
||||||
maxLength: 256,
|
nullable: false
|
||||||
nullable: false
|
),
|
||||||
),
|
name = table.Column<string>(type: "text", nullable: false),
|
||||||
name = table.Column<string>(type: "text", nullable: false),
|
tagline = table.Column<string>(type: "text", nullable: true),
|
||||||
tagline = table.Column<string>(type: "text", nullable: true),
|
aliases = table.Column<List<string>>(type: "text[]", nullable: false),
|
||||||
aliases = table.Column<List<string>>(type: "text[]", nullable: false),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true),
|
tags = table.Column<List<string>>(type: "text[]", nullable: false),
|
||||||
tags = table.Column<List<string>>(type: "text[]", nullable: false),
|
genres = table.Column<List<Genre>>(type: "genre[]", nullable: false),
|
||||||
genres = table.Column<List<Genre>>(type: "genre[]", nullable: false),
|
status = table.Column<Status>(type: "status", nullable: false),
|
||||||
status = table.Column<Status>(type: "status", nullable: false),
|
rating = table.Column<int>(type: "integer", nullable: false),
|
||||||
rating = table.Column<int>(type: "integer", nullable: false),
|
start_air = table.Column<DateTime>(
|
||||||
start_air = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
end_air = table.Column<DateTime>(
|
||||||
end_air = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
poster_blurhash = table.Column<string>(
|
||||||
poster_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
thumbnail_blurhash = table.Column<string>(
|
||||||
thumbnail_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
logo_blurhash = table.Column<string>(
|
||||||
logo_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
trailer = table.Column<string>(type: "text", nullable: true),
|
||||||
trailer = table.Column<string>(type: "text", nullable: true),
|
external_id = table.Column<string>(type: "json", nullable: false),
|
||||||
external_id = table.Column<string>(type: "json", nullable: false),
|
studio_id = table.Column<Guid>(type: "uuid", nullable: true)
|
||||||
studio_id = table.Column<Guid>(type: "uuid", nullable: true)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_shows", x => x.id);
|
table.PrimaryKey("pk_shows", x => x.id);
|
||||||
@ -270,12 +265,11 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "link_collection_movie",
|
name: "link_collection_movie",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
collection_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
collection_id = table.Column<Guid>(type: "uuid", nullable: false),
|
movie_id = table.Column<Guid>(type: "uuid", nullable: false)
|
||||||
movie_id = table.Column<Guid>(type: "uuid", nullable: false)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey(
|
table.PrimaryKey(
|
||||||
@ -301,12 +295,11 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "link_collection_show",
|
name: "link_collection_show",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
collection_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
collection_id = table.Column<Guid>(type: "uuid", nullable: false),
|
show_id = table.Column<Guid>(type: "uuid", nullable: false)
|
||||||
show_id = table.Column<Guid>(type: "uuid", nullable: false)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey(
|
table.PrimaryKey(
|
||||||
@ -332,52 +325,51 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "seasons",
|
name: "seasons",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
slug = table.Column<string>(
|
||||||
slug = table.Column<string>(
|
type: "character varying(256)",
|
||||||
type: "character varying(256)",
|
maxLength: 256,
|
||||||
maxLength: 256,
|
nullable: false
|
||||||
nullable: false
|
),
|
||||||
),
|
show_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
show_id = table.Column<Guid>(type: "uuid", nullable: false),
|
season_number = table.Column<int>(type: "integer", nullable: false),
|
||||||
season_number = table.Column<int>(type: "integer", nullable: false),
|
name = table.Column<string>(type: "text", nullable: true),
|
||||||
name = table.Column<string>(type: "text", nullable: true),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true),
|
start_date = table.Column<DateTime>(
|
||||||
start_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
end_date = table.Column<DateTime>(
|
||||||
end_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
poster_blurhash = table.Column<string>(
|
||||||
poster_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
thumbnail_blurhash = table.Column<string>(
|
||||||
thumbnail_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
logo_blurhash = table.Column<string>(
|
||||||
logo_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
external_id = table.Column<string>(type: "json", nullable: false)
|
||||||
external_id = table.Column<string>(type: "json", nullable: false)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_seasons", x => x.id);
|
table.PrimaryKey("pk_seasons", x => x.id);
|
||||||
@ -393,53 +385,52 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "episodes",
|
name: "episodes",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
slug = table.Column<string>(
|
||||||
slug = table.Column<string>(
|
type: "character varying(256)",
|
||||||
type: "character varying(256)",
|
maxLength: 256,
|
||||||
maxLength: 256,
|
nullable: false
|
||||||
nullable: false
|
),
|
||||||
),
|
show_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
show_id = table.Column<Guid>(type: "uuid", nullable: false),
|
season_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
season_id = table.Column<Guid>(type: "uuid", nullable: true),
|
season_number = table.Column<int>(type: "integer", nullable: true),
|
||||||
season_number = table.Column<int>(type: "integer", nullable: true),
|
episode_number = table.Column<int>(type: "integer", nullable: true),
|
||||||
episode_number = table.Column<int>(type: "integer", nullable: true),
|
absolute_number = table.Column<int>(type: "integer", nullable: true),
|
||||||
absolute_number = table.Column<int>(type: "integer", nullable: true),
|
path = table.Column<string>(type: "text", nullable: false),
|
||||||
path = table.Column<string>(type: "text", nullable: false),
|
name = table.Column<string>(type: "text", nullable: true),
|
||||||
name = table.Column<string>(type: "text", nullable: true),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true),
|
runtime = table.Column<int>(type: "integer", nullable: false),
|
||||||
runtime = table.Column<int>(type: "integer", nullable: false),
|
release_date = table.Column<DateTime>(
|
||||||
release_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
poster_source = table.Column<string>(type: "text", nullable: true),
|
||||||
poster_source = table.Column<string>(type: "text", nullable: true),
|
poster_blurhash = table.Column<string>(
|
||||||
poster_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
||||||
thumbnail_source = table.Column<string>(type: "text", nullable: true),
|
thumbnail_blurhash = table.Column<string>(
|
||||||
thumbnail_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
logo_source = table.Column<string>(type: "text", nullable: true),
|
||||||
logo_source = table.Column<string>(type: "text", nullable: true),
|
logo_blurhash = table.Column<string>(
|
||||||
logo_blurhash = table.Column<string>(
|
type: "character varying(32)",
|
||||||
type: "character varying(32)",
|
maxLength: 32,
|
||||||
maxLength: 32,
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
external_id = table.Column<string>(type: "json", nullable: false)
|
||||||
external_id = table.Column<string>(type: "json", nullable: false)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_episodes", x => x.id);
|
table.PrimaryKey("pk_episodes", x => x.id);
|
||||||
|
@ -46,24 +46,23 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "episode_watch_status",
|
name: "episode_watch_status",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
episode_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
episode_id = table.Column<Guid>(type: "uuid", nullable: false),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
played_date = table.Column<DateTime>(
|
||||||
played_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
status = table.Column<WatchStatus>(type: "watch_status", nullable: false),
|
||||||
status = table.Column<WatchStatus>(type: "watch_status", nullable: false),
|
watched_time = table.Column<int>(type: "integer", nullable: true),
|
||||||
watched_time = table.Column<int>(type: "integer", nullable: true),
|
watched_percent = table.Column<int>(type: "integer", nullable: true)
|
||||||
watched_percent = table.Column<int>(type: "integer", nullable: true)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey(
|
table.PrimaryKey(
|
||||||
@ -89,24 +88,23 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "movie_watch_status",
|
name: "movie_watch_status",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
movie_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
movie_id = table.Column<Guid>(type: "uuid", nullable: false),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
played_date = table.Column<DateTime>(
|
||||||
played_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
status = table.Column<WatchStatus>(type: "watch_status", nullable: false),
|
||||||
status = table.Column<WatchStatus>(type: "watch_status", nullable: false),
|
watched_time = table.Column<int>(type: "integer", nullable: true),
|
||||||
watched_time = table.Column<int>(type: "integer", nullable: true),
|
watched_percent = table.Column<int>(type: "integer", nullable: true)
|
||||||
watched_percent = table.Column<int>(type: "integer", nullable: true)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_movie_watch_status", x => new { x.user_id, x.movie_id });
|
table.PrimaryKey("pk_movie_watch_status", x => new { x.user_id, x.movie_id });
|
||||||
@ -129,26 +127,25 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "show_watch_status",
|
name: "show_watch_status",
|
||||||
columns: table =>
|
columns: table => new
|
||||||
new
|
{
|
||||||
{
|
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
user_id = table.Column<Guid>(type: "uuid", nullable: false),
|
show_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
show_id = table.Column<Guid>(type: "uuid", nullable: false),
|
added_date = table.Column<DateTime>(
|
||||||
added_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: false,
|
||||||
nullable: false,
|
defaultValueSql: "now() at time zone 'utc'"
|
||||||
defaultValueSql: "now() at time zone 'utc'"
|
),
|
||||||
),
|
played_date = table.Column<DateTime>(
|
||||||
played_date = table.Column<DateTime>(
|
type: "timestamp with time zone",
|
||||||
type: "timestamp with time zone",
|
nullable: true
|
||||||
nullable: true
|
),
|
||||||
),
|
status = table.Column<WatchStatus>(type: "watch_status", nullable: false),
|
||||||
status = table.Column<WatchStatus>(type: "watch_status", nullable: false),
|
unseen_episodes_count = table.Column<int>(type: "integer", nullable: false),
|
||||||
unseen_episodes_count = table.Column<int>(type: "integer", nullable: false),
|
next_episode_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
next_episode_id = table.Column<Guid>(type: "uuid", nullable: true),
|
watched_time = table.Column<int>(type: "integer", nullable: true),
|
||||||
watched_time = table.Column<int>(type: "integer", nullable: true),
|
watched_percent = table.Column<int>(type: "integer", nullable: true)
|
||||||
watched_percent = table.Column<int>(type: "integer", nullable: true)
|
},
|
||||||
},
|
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_show_watch_status", x => new { x.user_id, x.show_id });
|
table.PrimaryKey("pk_show_watch_status", x => new { x.user_id, x.show_id });
|
||||||
|
@ -99,17 +99,14 @@ namespace Kyoo.Postgresql
|
|||||||
|
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)
|
.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)
|
||||||
.HasTranslation(
|
.HasTranslation(args => new SqlFunctionExpression(
|
||||||
args =>
|
"md5",
|
||||||
new SqlFunctionExpression(
|
args,
|
||||||
"md5",
|
nullable: true,
|
||||||
args,
|
argumentsPropagateNullability: new[] { false },
|
||||||
nullable: true,
|
type: args[0].Type,
|
||||||
argumentsPropagateNullability: new[] { false },
|
typeMapping: args[0].TypeMapping
|
||||||
type: args[0].Type,
|
));
|
||||||
typeMapping: args[0].TypeMapping
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,7 @@ namespace Kyoo.Swagger
|
|||||||
{
|
{
|
||||||
// We can't reorder items by assigning the sorted value to the Paths variable since it has no setter.
|
// We can't reorder items by assigning the sorted value to the Paths variable since it has no setter.
|
||||||
List<KeyValuePair<string, OpenApiPathItem>> sorted = postProcess
|
List<KeyValuePair<string, OpenApiPathItem>> sorted = postProcess
|
||||||
.Paths
|
.Paths.OrderBy(x => x.Key)
|
||||||
.OrderBy(x => x.Key)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
postProcess.Paths.Clear();
|
postProcess.Paths.Clear();
|
||||||
foreach ((string key, OpenApiPathItem value) in sorted)
|
foreach ((string key, OpenApiPathItem value) in sorted)
|
||||||
|
@ -21,10 +21,10 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
using Kyoo.Swagger.Models;
|
using Kyoo.Swagger.Models;
|
||||||
|
using Namotion.Reflection;
|
||||||
using NSwag;
|
using NSwag;
|
||||||
using NSwag.Generation.AspNetCore;
|
using NSwag.Generation.AspNetCore;
|
||||||
using NSwag.Generation.Processors.Contexts;
|
using NSwag.Generation.Processors.Contexts;
|
||||||
using Namotion.Reflection;
|
|
||||||
|
|
||||||
namespace Kyoo.Swagger
|
namespace Kyoo.Swagger
|
||||||
{
|
{
|
||||||
@ -42,30 +42,25 @@ namespace Kyoo.Swagger
|
|||||||
/// <returns>This always return <c>true</c> since it should not remove operations.</returns>
|
/// <returns>This always return <c>true</c> since it should not remove operations.</returns>
|
||||||
public static bool OperationFilter(OperationProcessorContext context)
|
public static bool OperationFilter(OperationProcessorContext context)
|
||||||
{
|
{
|
||||||
ApiDefinitionAttribute def = context
|
ApiDefinitionAttribute def =
|
||||||
.ControllerType
|
context.ControllerType.GetCustomAttribute<ApiDefinitionAttribute>();
|
||||||
.GetCustomAttribute<ApiDefinitionAttribute>();
|
|
||||||
string name = def?.Name ?? context.ControllerType.Name;
|
string name = def?.Name ?? context.ControllerType.Name;
|
||||||
|
|
||||||
ApiDefinitionAttribute methodOverride = context
|
ApiDefinitionAttribute methodOverride =
|
||||||
.MethodInfo
|
context.MethodInfo.GetCustomAttribute<ApiDefinitionAttribute>();
|
||||||
.GetCustomAttribute<ApiDefinitionAttribute>();
|
|
||||||
if (methodOverride != null)
|
if (methodOverride != null)
|
||||||
name = methodOverride.Name;
|
name = methodOverride.Name;
|
||||||
|
|
||||||
context.OperationDescription.Operation.Tags.Add(name);
|
context.OperationDescription.Operation.Tags.Add(name);
|
||||||
if (context.Document.Tags.All(x => x.Name != name))
|
if (context.Document.Tags.All(x => x.Name != name))
|
||||||
{
|
{
|
||||||
context
|
context.Document.Tags.Add(
|
||||||
.Document
|
new OpenApiTag
|
||||||
.Tags
|
{
|
||||||
.Add(
|
Name = name,
|
||||||
new OpenApiTag
|
Description = context.ControllerType.GetXmlDocsSummary()
|
||||||
{
|
}
|
||||||
Name = name,
|
);
|
||||||
Description = context.ControllerType.GetXmlDocsSummary()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (def?.Group == null)
|
if (def?.Group == null)
|
||||||
@ -106,8 +101,7 @@ namespace Kyoo.Swagger
|
|||||||
{
|
{
|
||||||
List<TagGroups> tagGroups = (List<TagGroups>)postProcess.ExtensionData["x-tagGroups"];
|
List<TagGroups> tagGroups = (List<TagGroups>)postProcess.ExtensionData["x-tagGroups"];
|
||||||
List<string> tagsWithoutGroup = postProcess
|
List<string> tagsWithoutGroup = postProcess
|
||||||
.Tags
|
.Tags.Select(x => x.Name)
|
||||||
.Select(x => x.Name)
|
|
||||||
.Where(x => tagGroups.SelectMany(y => y.Tags).All(y => y != x))
|
.Where(x => tagGroups.SelectMany(y => y.Tags).All(y => y != x))
|
||||||
.ToList();
|
.ToList();
|
||||||
if (tagsWithoutGroup.Any())
|
if (tagsWithoutGroup.Any())
|
||||||
|
@ -49,8 +49,7 @@ namespace Kyoo.Swagger
|
|||||||
foreach (ActionModel action in context.Result.Controllers.SelectMany(x => x.Actions))
|
foreach (ActionModel action in context.Result.Controllers.SelectMany(x => x.Actions))
|
||||||
{
|
{
|
||||||
IEnumerable<ProducesResponseTypeAttribute> responses = action
|
IEnumerable<ProducesResponseTypeAttribute> responses = action
|
||||||
.Filters
|
.Filters.OfType<ProducesResponseTypeAttribute>()
|
||||||
.OfType<ProducesResponseTypeAttribute>()
|
|
||||||
.Where(x => x.Type == typeof(ActionResult<>));
|
.Where(x => x.Type == typeof(ActionResult<>));
|
||||||
foreach (ProducesResponseTypeAttribute response in responses)
|
foreach (ProducesResponseTypeAttribute response in responses)
|
||||||
{
|
{
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NSwag;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using NSwag;
|
||||||
|
|
||||||
namespace Kyoo.Swagger.Models
|
namespace Kyoo.Swagger.Models
|
||||||
{
|
{
|
||||||
|
@ -39,8 +39,7 @@ namespace Kyoo.Swagger
|
|||||||
context.OperationDescription.Operation.Security ??=
|
context.OperationDescription.Operation.Security ??=
|
||||||
new List<OpenApiSecurityRequirement>();
|
new List<OpenApiSecurityRequirement>();
|
||||||
OpenApiSecurityRequirement perms = context
|
OpenApiSecurityRequirement perms = context
|
||||||
.MethodInfo
|
.MethodInfo.GetCustomAttributes<UserOnlyAttribute>()
|
||||||
.GetCustomAttributes<UserOnlyAttribute>()
|
|
||||||
.Aggregate(
|
.Aggregate(
|
||||||
new OpenApiSecurityRequirement(),
|
new OpenApiSecurityRequirement(),
|
||||||
(agg, _) =>
|
(agg, _) =>
|
||||||
@ -51,8 +50,7 @@ namespace Kyoo.Swagger
|
|||||||
);
|
);
|
||||||
|
|
||||||
perms = context
|
perms = context
|
||||||
.MethodInfo
|
.MethodInfo.GetCustomAttributes<PermissionAttribute>()
|
||||||
.GetCustomAttributes<PermissionAttribute>()
|
|
||||||
.Aggregate(
|
.Aggregate(
|
||||||
perms,
|
perms,
|
||||||
(agg, cur) =>
|
(agg, cur) =>
|
||||||
@ -64,14 +62,12 @@ namespace Kyoo.Swagger
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
PartialPermissionAttribute controller = context
|
PartialPermissionAttribute controller =
|
||||||
.ControllerType
|
context.ControllerType.GetCustomAttribute<PartialPermissionAttribute>();
|
||||||
.GetCustomAttribute<PartialPermissionAttribute>();
|
|
||||||
if (controller != null)
|
if (controller != null)
|
||||||
{
|
{
|
||||||
perms = context
|
perms = context
|
||||||
.MethodInfo
|
.MethodInfo.GetCustomAttributes<PartialPermissionAttribute>()
|
||||||
.GetCustomAttributes<PartialPermissionAttribute>()
|
|
||||||
.Aggregate(
|
.Aggregate(
|
||||||
perms,
|
perms,
|
||||||
(agg, cur) =>
|
(agg, cur) =>
|
||||||
|
@ -82,20 +82,16 @@ namespace Kyoo.Swagger
|
|||||||
!= AlternativeRoute;
|
!= AlternativeRoute;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
document
|
document.SchemaGenerator.Settings.TypeMappers.Add(
|
||||||
.SchemaGenerator
|
new PrimitiveTypeMapper(
|
||||||
.Settings
|
typeof(Identifier),
|
||||||
.TypeMappers
|
x =>
|
||||||
.Add(
|
{
|
||||||
new PrimitiveTypeMapper(
|
x.IsNullableRaw = false;
|
||||||
typeof(Identifier),
|
x.Type = JsonObjectType.String | JsonObjectType.Integer;
|
||||||
x =>
|
}
|
||||||
{
|
)
|
||||||
x.IsNullableRaw = false;
|
);
|
||||||
x.Type = JsonObjectType.String | JsonObjectType.Integer;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
document.AddSecurity(
|
document.AddSecurity(
|
||||||
nameof(Kyoo),
|
nameof(Kyoo),
|
||||||
|
@ -54,17 +54,14 @@ namespace Kyoo.Tests.Database
|
|||||||
{
|
{
|
||||||
Episode episode = await _repository.Get(1.AsGuid());
|
Episode episode = await _repository.Get(1.AsGuid());
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
|
||||||
await Repositories
|
await Repositories.LibraryManager.Shows.Patch(
|
||||||
.LibraryManager
|
episode.ShowId,
|
||||||
.Shows
|
(x) =>
|
||||||
.Patch(
|
{
|
||||||
episode.ShowId,
|
x.Slug = "new-slug";
|
||||||
(x) =>
|
return x;
|
||||||
{
|
}
|
||||||
x.Slug = "new-slug";
|
);
|
||||||
return x;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
episode = await _repository.Get(1.AsGuid());
|
episode = await _repository.Get(1.AsGuid());
|
||||||
Assert.Equal("new-slug-s1e1", episode.Slug);
|
Assert.Equal("new-slug-s1e1", episode.Slug);
|
||||||
}
|
}
|
||||||
@ -92,17 +89,14 @@ namespace Kyoo.Tests.Database
|
|||||||
{
|
{
|
||||||
Episode episode = await _repository.Get(1.AsGuid());
|
Episode episode = await _repository.Get(1.AsGuid());
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e1", episode.Slug);
|
||||||
episode = await Repositories
|
episode = await Repositories.LibraryManager.Episodes.Patch(
|
||||||
.LibraryManager
|
episode.Id,
|
||||||
.Episodes
|
(x) =>
|
||||||
.Patch(
|
{
|
||||||
episode.Id,
|
x.EpisodeNumber = 2;
|
||||||
(x) =>
|
return x;
|
||||||
{
|
}
|
||||||
x.EpisodeNumber = 2;
|
);
|
||||||
return x;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||||
episode = await _repository.Get(1.AsGuid());
|
episode = await _repository.Get(1.AsGuid());
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||||
@ -143,17 +137,14 @@ namespace Kyoo.Tests.Database
|
|||||||
public async Task SlugEditAbsoluteTest()
|
public async Task SlugEditAbsoluteTest()
|
||||||
{
|
{
|
||||||
Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode());
|
Episode episode = await _repository.Create(TestSample.GetAbsoluteEpisode());
|
||||||
await Repositories
|
await Repositories.LibraryManager.Shows.Patch(
|
||||||
.LibraryManager
|
episode.ShowId,
|
||||||
.Shows
|
(x) =>
|
||||||
.Patch(
|
{
|
||||||
episode.ShowId,
|
x.Slug = "new-slug";
|
||||||
(x) =>
|
return x;
|
||||||
{
|
}
|
||||||
x.Slug = "new-slug";
|
);
|
||||||
return x;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
episode = await _repository.Get(2.AsGuid());
|
episode = await _repository.Get(2.AsGuid());
|
||||||
Assert.Equal($"new-slug-3", episode.Slug);
|
Assert.Equal($"new-slug-3", episode.Slug);
|
||||||
}
|
}
|
||||||
|
@ -53,17 +53,14 @@ namespace Kyoo.Tests.Database
|
|||||||
{
|
{
|
||||||
Season season = await _repository.Get(1.AsGuid());
|
Season season = await _repository.Get(1.AsGuid());
|
||||||
Assert.Equal("anohana-s1", season.Slug);
|
Assert.Equal("anohana-s1", season.Slug);
|
||||||
await Repositories
|
await Repositories.LibraryManager.Shows.Patch(
|
||||||
.LibraryManager
|
season.ShowId,
|
||||||
.Shows
|
(x) =>
|
||||||
.Patch(
|
{
|
||||||
season.ShowId,
|
x.Slug = "new-slug";
|
||||||
(x) =>
|
return x;
|
||||||
{
|
}
|
||||||
x.Slug = "new-slug";
|
);
|
||||||
return x;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
season = await _repository.Get(1.AsGuid());
|
season = await _repository.Get(1.AsGuid());
|
||||||
Assert.Equal("new-slug-s1", season.Slug);
|
Assert.Equal("new-slug-s1", season.Slug);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user