mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-01 20:54:13 -04:00
Handle next episode position/percent in show watch status
This commit is contained in:
parent
e124113d41
commit
bd48032a50
@ -62,9 +62,18 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filter">A predicate to filter the resource.</param>
|
/// <param name="filter">A predicate to filter the resource.</param>
|
||||||
/// <param name="include">The related fields to include.</param>
|
/// <param name="include">The related fields to include.</param>
|
||||||
|
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
|
||||||
|
/// <param name="reverse">Reverse the sort.</param>
|
||||||
|
/// <param name="afterId">Select the first element after this id if it was in a list.</param>
|
||||||
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
||||||
/// <returns>The resource found</returns>
|
/// <returns>The resource found</returns>
|
||||||
Task<T> Get(Filter<T> filter, Include<T>? include = default);
|
Task<T> Get(
|
||||||
|
Filter<T> filter,
|
||||||
|
Include<T>? include = default,
|
||||||
|
Sort<T>? sortBy = default,
|
||||||
|
bool reverse = false,
|
||||||
|
Guid? afterId = default
|
||||||
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a resource from it's ID or null if it is not found.
|
/// Get a resource from it's ID or null if it is not found.
|
||||||
@ -89,11 +98,13 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="include">The related fields to include.</param>
|
/// <param name="include">The related fields to include.</param>
|
||||||
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
|
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
|
||||||
/// <param name="reverse">Reverse the sort.</param>
|
/// <param name="reverse">Reverse the sort.</param>
|
||||||
|
/// <param name="afterId">Select the first element after this id if it was in a list.</param>
|
||||||
/// <returns>The resource found</returns>
|
/// <returns>The resource found</returns>
|
||||||
Task<T?> GetOrDefault(Filter<T>? filter,
|
Task<T?> GetOrDefault(Filter<T>? filter,
|
||||||
Include<T>? include = default,
|
Include<T>? include = default,
|
||||||
Sort<T>? sortBy = default,
|
Sort<T>? sortBy = default,
|
||||||
bool reverse = false);
|
bool reverse = false,
|
||||||
|
Guid? afterId = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Search for resources with the database.
|
/// Search for resources with the database.
|
||||||
|
@ -216,22 +216,14 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Null if the status is not Watching or if the next episode is not started.
|
/// Null if the status is not Watching or if the next episode is not started.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Projectable(UseMemberBody = nameof(_WatchedTime), NullConditionalRewriteSupport = NullConditionalRewriteSupport.Ignore)]
|
|
||||||
[NotMapped]
|
|
||||||
public int? WatchedTime { get; set; }
|
public int? WatchedTime { get; set; }
|
||||||
|
|
||||||
private int? _WatchedTime => NextEpisode?.Watched!.FirstOrDefault()?.WatchedTime;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where the player has stopped watching the episode (in percentage between 0 and 100).
|
/// Where the player has stopped watching the episode (in percentage between 0 and 100).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Null if the status is not Watching or if the next episode is not started.
|
/// Null if the status is not Watching or if the next episode is not started.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Projectable(UseMemberBody = nameof(_WatchedPercent), NullConditionalRewriteSupport = NullConditionalRewriteSupport.Ignore)]
|
|
||||||
[NotMapped]
|
|
||||||
public int? WatchedPercent { get; set; }
|
public int? WatchedPercent { get; set; }
|
||||||
|
|
||||||
private int? _WatchedPercent => NextEpisode?.Watched!.FirstOrDefault()?.WatchedPercent;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,15 +52,12 @@ public class Include<T> : Include
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<string> Fields => Metadatas.Select(x => x.Name).ToList();
|
public ICollection<string> Fields => Metadatas.Select(x => x.Name).ToList();
|
||||||
|
|
||||||
public static Include<T> From(string? fields)
|
public Include() { }
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(fields))
|
|
||||||
return new Include<T>();
|
|
||||||
|
|
||||||
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
|
public Include(params string[] fields)
|
||||||
return new Include<T>
|
|
||||||
{
|
{
|
||||||
Metadatas = fields.Split(',').SelectMany(key =>
|
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
|
||||||
|
Metadatas = fields.SelectMany(key =>
|
||||||
{
|
{
|
||||||
var relations = types
|
var relations = types
|
||||||
.Select(x => x.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)!)
|
.Select(x => x.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)!)
|
||||||
@ -76,18 +73,6 @@ public class Include<T> : Include
|
|||||||
|
|
||||||
if (attr.RelationID != null)
|
if (attr.RelationID != null)
|
||||||
return new SingleRelation(prop.Name, prop.PropertyType, attr.RelationID) as Metadata;
|
return new SingleRelation(prop.Name, prop.PropertyType, attr.RelationID) as Metadata;
|
||||||
|
|
||||||
// Multiples relations are disabled due to:
|
|
||||||
// - Cartesian Explosions perfs
|
|
||||||
// - Code complexity added.
|
|
||||||
// if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType != typeof(string))
|
|
||||||
// {
|
|
||||||
// // The property is either a list or a an array.
|
|
||||||
// return new MultipleRelation(
|
|
||||||
// prop.Name,
|
|
||||||
// prop.PropertyType.GetElementType() ?? prop.PropertyType.GenericTypeArguments.First()
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
if (attr.Sql != null)
|
if (attr.Sql != null)
|
||||||
return new CustomRelation(prop.Name, prop.PropertyType, attr.Sql, attr.On, prop.DeclaringType!);
|
return new CustomRelation(prop.Name, prop.PropertyType, attr.Sql, attr.On, prop.DeclaringType!);
|
||||||
if (attr.Projected != null)
|
if (attr.Projected != null)
|
||||||
@ -95,7 +80,13 @@ public class Include<T> : Include
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
})
|
})
|
||||||
.Distinct();
|
.Distinct();
|
||||||
}).ToArray()
|
}).ToArray();
|
||||||
};
|
}
|
||||||
|
|
||||||
|
public static Include<T> From(string? fields)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fields))
|
||||||
|
return new Include<T>();
|
||||||
|
return new Include<T>(fields.Split(','));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,7 +321,8 @@ public static class DapperHelper
|
|||||||
Include<T>? include,
|
Include<T>? include,
|
||||||
Filter<T>? filter,
|
Filter<T>? filter,
|
||||||
Sort<T>? sort = null,
|
Sort<T>? sort = null,
|
||||||
bool reverse = false)
|
bool reverse = false,
|
||||||
|
Guid? afterId = default)
|
||||||
where T : class, IResource, IQuery
|
where T : class, IResource, IQuery
|
||||||
{
|
{
|
||||||
ICollection<T> ret = await db.Query<T>(
|
ICollection<T> ret = await db.Query<T>(
|
||||||
@ -333,7 +334,7 @@ public static class DapperHelper
|
|||||||
include,
|
include,
|
||||||
filter,
|
filter,
|
||||||
sort,
|
sort,
|
||||||
new Pagination(1, reverse: reverse)
|
new Pagination(1, afterId, reverse)
|
||||||
);
|
);
|
||||||
return ret.FirstOrDefault();
|
return ret.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
@ -69,10 +69,13 @@ public abstract class DapperRepository<T> : IRepository<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async Task<T> Get(Filter<T> filter,
|
public virtual async Task<T> Get(Filter<T>? filter,
|
||||||
Include<T>? include = default)
|
Include<T>? include = default,
|
||||||
|
Sort<T>? sortBy = default,
|
||||||
|
bool reverse = false,
|
||||||
|
Guid? afterId = default)
|
||||||
{
|
{
|
||||||
T? ret = await GetOrDefault(filter, include: include);
|
T? ret = await GetOrDefault(filter, include, sortBy, reverse, afterId);
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
|
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
|
||||||
return ret;
|
return ret;
|
||||||
@ -135,10 +138,11 @@ public abstract class DapperRepository<T> : IRepository<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<T?> GetOrDefault(Filter<T>? filter,
|
public virtual Task<T?> GetOrDefault(Filter<T>? filter,
|
||||||
Include<T>? include = null,
|
Include<T>? include = default,
|
||||||
Sort<T>? sortBy = null,
|
Sort<T>? sortBy = default,
|
||||||
bool reverse = false)
|
bool reverse = false,
|
||||||
|
Guid? afterId = default)
|
||||||
{
|
{
|
||||||
return Database.QuerySingle<T>(
|
return Database.QuerySingle<T>(
|
||||||
Sql,
|
Sql,
|
||||||
@ -147,7 +151,9 @@ public abstract class DapperRepository<T> : IRepository<T>
|
|||||||
Context,
|
Context,
|
||||||
include,
|
include,
|
||||||
filter,
|
filter,
|
||||||
sortBy
|
sortBy,
|
||||||
|
reverse,
|
||||||
|
afterId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,9 +221,15 @@ namespace Kyoo.Core.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual async Task<T> Get(Filter<T> filter, Include<T>? include = default)
|
public virtual async Task<T> Get(
|
||||||
|
Filter<T> filter,
|
||||||
|
Include<T>? include = default,
|
||||||
|
Sort<T>? sortBy = default,
|
||||||
|
bool reverse = false,
|
||||||
|
Guid? afterId = default
|
||||||
|
)
|
||||||
{
|
{
|
||||||
T? ret = await GetOrDefault(filter, include: include);
|
T? ret = await GetOrDefault(filter, include, sortBy, reverse, afterId);
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
|
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
|
||||||
return ret;
|
return ret;
|
||||||
@ -255,18 +261,20 @@ namespace Kyoo.Core.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual Task<T?> GetOrDefault(Filter<T>? filter,
|
public virtual async Task<T?> GetOrDefault(Filter<T>? filter,
|
||||||
Include<T>? include = default,
|
Include<T>? include = default,
|
||||||
Sort<T>? sortBy = default,
|
Sort<T>? sortBy = default,
|
||||||
bool reverse = false)
|
bool reverse = false,
|
||||||
|
Guid? afterId = default)
|
||||||
{
|
{
|
||||||
IQueryable<T> query = Sort(
|
IQueryable<T> query = await ApplyFilters(
|
||||||
AddIncludes(Database.Set<T>(), include),
|
Database.Set<T>(),
|
||||||
sortBy
|
filter,
|
||||||
|
sortBy,
|
||||||
|
new Pagination(1, afterId, reverse),
|
||||||
|
include
|
||||||
);
|
);
|
||||||
if (reverse)
|
return await query.FirstOrDefaultAsync();
|
||||||
query = query.Reverse();
|
|
||||||
return query.FirstOrDefaultAsync(ParseFilter(filter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -285,12 +293,13 @@ namespace Kyoo.Core.Controllers
|
|||||||
public abstract Task<ICollection<T>> Search(string query, Include<T>? include = default);
|
public abstract Task<ICollection<T>> Search(string query, Include<T>? include = default);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public virtual Task<ICollection<T>> GetAll(Filter<T>? filter = null,
|
public virtual async Task<ICollection<T>> GetAll(Filter<T>? filter = null,
|
||||||
Sort<T>? sort = default,
|
Sort<T>? sort = default,
|
||||||
Include<T>? include = default,
|
Include<T>? include = default,
|
||||||
Pagination? limit = default)
|
Pagination? limit = default)
|
||||||
{
|
{
|
||||||
return ApplyFilters(Database.Set<T>(), filter, sort, limit, include);
|
IQueryable<T> query = await ApplyFilters(Database.Set<T>(), filter, sort, limit, include);
|
||||||
|
return await query.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -302,7 +311,7 @@ namespace Kyoo.Core.Controllers
|
|||||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||||
/// <param name="include">Related fields to also load with this query.</param>
|
/// <param name="include">Related fields to also load with this query.</param>
|
||||||
/// <returns>The filtered query</returns>
|
/// <returns>The filtered query</returns>
|
||||||
protected async Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
|
protected async Task<IQueryable<T>> ApplyFilters(IQueryable<T> query,
|
||||||
Filter<T>? filter = null,
|
Filter<T>? filter = null,
|
||||||
Sort<T>? sort = default,
|
Sort<T>? sort = default,
|
||||||
Pagination? limit = default,
|
Pagination? limit = default,
|
||||||
@ -317,7 +326,6 @@ namespace Kyoo.Core.Controllers
|
|||||||
T reference = await Get(limit.AfterID.Value);
|
T reference = await Get(limit.AfterID.Value);
|
||||||
Filter<T>? keysetFilter = RepositoryHelper.KeysetPaginate(sort, reference, !limit.Reverse);
|
Filter<T>? keysetFilter = RepositoryHelper.KeysetPaginate(sort, reference, !limit.Reverse);
|
||||||
filter = Filter.And(filter, keysetFilter);
|
filter = Filter.And(filter, keysetFilter);
|
||||||
Console.WriteLine(filter);
|
|
||||||
}
|
}
|
||||||
if (filter != null)
|
if (filter != null)
|
||||||
query = query.Where(ParseFilter(filter));
|
query = query.Where(ParseFilter(filter));
|
||||||
@ -329,7 +337,7 @@ namespace Kyoo.Core.Controllers
|
|||||||
if (limit.Reverse)
|
if (limit.Reverse)
|
||||||
query = query.Reverse();
|
query = query.Reverse();
|
||||||
|
|
||||||
return await query.ToListAsync();
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
@ -98,7 +99,8 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
MovieId = movieId,
|
MovieId = movieId,
|
||||||
Status = status,
|
Status = status,
|
||||||
WatchedTime = watchedTime,
|
WatchedTime = watchedTime,
|
||||||
PlayedDate = DateTime.UtcNow
|
AddedDate = DateTime.UtcNow,
|
||||||
|
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
||||||
};
|
};
|
||||||
await _database.MovieWatchStatus.Upsert(ret)
|
await _database.MovieWatchStatus.Upsert(ret)
|
||||||
.UpdateIf(x => status != Watching || x.Status != Completed)
|
.UpdateIf(x => status != Watching || x.Status != Completed)
|
||||||
@ -135,23 +137,44 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
if (unseenEpisodeCount == 0)
|
if (unseenEpisodeCount == 0)
|
||||||
status = WatchStatus.Completed;
|
status = WatchStatus.Completed;
|
||||||
|
|
||||||
|
Episode? cursor = null;
|
||||||
|
Guid? nextEpisodeId = null;
|
||||||
|
if (status == WatchStatus.Watching)
|
||||||
|
{
|
||||||
|
cursor = await _episodes.GetOrDefault(
|
||||||
|
new Filter<Episode>.Lambda(
|
||||||
|
x => x.ShowId == showId
|
||||||
|
&& (x.WatchStatus!.Status == WatchStatus.Completed
|
||||||
|
|| x.WatchStatus.Status == WatchStatus.Watching)
|
||||||
|
),
|
||||||
|
new Include<Episode>(nameof(Episode.WatchStatus)),
|
||||||
|
reverse: true
|
||||||
|
);
|
||||||
|
nextEpisodeId = cursor?.WatchStatus?.Status == WatchStatus.Watching
|
||||||
|
? cursor.Id
|
||||||
|
: ((await _episodes.GetOrDefault(
|
||||||
|
new Filter<Episode>.Lambda(
|
||||||
|
x => x.ShowId == showId && x.WatchStatus!.Status != WatchStatus.Completed
|
||||||
|
),
|
||||||
|
afterId: cursor?.Id
|
||||||
|
))?.Id);
|
||||||
|
}
|
||||||
|
|
||||||
ShowWatchStatus ret = new()
|
ShowWatchStatus ret = new()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
ShowId = showId,
|
ShowId = showId,
|
||||||
Status = status,
|
Status = status,
|
||||||
NextEpisode = status == WatchStatus.Watching
|
AddedDate = DateTime.UtcNow,
|
||||||
? await _episodes.GetOrDefault(
|
NextEpisodeId = nextEpisodeId,
|
||||||
new Filter<Episode>.Lambda(
|
WatchedTime = cursor?.WatchStatus?.Status == WatchStatus.Watching
|
||||||
x => x.ShowId == showId
|
? cursor.WatchStatus.WatchedTime
|
||||||
&& (x.WatchStatus!.Status == WatchStatus.Watching
|
: null,
|
||||||
|| x.WatchStatus.Status == WatchStatus.Completed)
|
WatchedPercent = cursor?.WatchStatus?.Status == WatchStatus.Watching
|
||||||
),
|
? cursor.WatchStatus.WatchedPercent
|
||||||
reverse: true
|
|
||||||
)
|
|
||||||
: null,
|
: null,
|
||||||
UnseenEpisodesCount = unseenEpisodeCount,
|
UnseenEpisodesCount = unseenEpisodeCount,
|
||||||
PlayedDate = DateTime.UtcNow
|
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
||||||
};
|
};
|
||||||
await _database.ShowWatchStatus.Upsert(ret)
|
await _database.ShowWatchStatus.Upsert(ret)
|
||||||
.UpdateIf(x => status != Watching || x.Status != Completed)
|
.UpdateIf(x => status != Watching || x.Status != Completed)
|
||||||
@ -210,7 +233,8 @@ public class WatchStatusRepository : IWatchStatusRepository
|
|||||||
Status = status,
|
Status = status,
|
||||||
WatchedTime = watchedTime,
|
WatchedTime = watchedTime,
|
||||||
WatchedPercent = percent,
|
WatchedPercent = percent,
|
||||||
PlayedDate = DateTime.UtcNow
|
AddedDate = DateTime.UtcNow,
|
||||||
|
PlayedDate = status == WatchStatus.Completed ? DateTime.UtcNow : null,
|
||||||
};
|
};
|
||||||
await _database.EpisodeWatchStatus.Upsert(ret)
|
await _database.EpisodeWatchStatus.Upsert(ret)
|
||||||
.UpdateIf(x => status != Watching || x.Status != Completed)
|
.UpdateIf(x => status != Watching || x.Status != Completed)
|
||||||
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
namespace Kyoo.Postgresql.Migrations
|
namespace Kyoo.Postgresql.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PostgresContext))]
|
[DbContext(typeof(PostgresContext))]
|
||||||
[Migration("20231203194301_Watchlist")]
|
[Migration("20231204000849_Watchlist")]
|
||||||
partial class Watchlist
|
partial class Watchlist
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -511,6 +511,14 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("unseen_episodes_count");
|
.HasColumnName("unseen_episodes_count");
|
||||||
|
|
||||||
|
b.Property<int?>("WatchedPercent")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("watched_percent");
|
||||||
|
|
||||||
|
b.Property<int?>("WatchedTime")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("watched_time");
|
||||||
|
|
||||||
b.HasKey("UserId", "ShowId")
|
b.HasKey("UserId", "ShowId")
|
||||||
.HasName("pk_show_watch_status");
|
.HasName("pk_show_watch_status");
|
||||||
|
|
@ -105,7 +105,9 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
played_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
played_date = table.Column<DateTime>(type: "timestamp with time zone", 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_percent = table.Column<int>(type: "integer", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
@ -508,6 +508,14 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("unseen_episodes_count");
|
.HasColumnName("unseen_episodes_count");
|
||||||
|
|
||||||
|
b.Property<int?>("WatchedPercent")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("watched_percent");
|
||||||
|
|
||||||
|
b.Property<int?>("WatchedTime")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("watched_time");
|
||||||
|
|
||||||
b.HasKey("UserId", "ShowId")
|
b.HasKey("UserId", "ShowId")
|
||||||
.HasName("pk_show_watch_status");
|
.HasName("pk_show_watch_status");
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user