Start watchlist implementations

This commit is contained in:
Zoe Roux 2023-11-12 17:15:41 +01:00
parent c1ba51b903
commit 4135fc5703
7 changed files with 166 additions and 24 deletions

View File

@ -39,5 +39,18 @@ namespace Kyoo.Authentication
return user.Claims.FirstOrDefault(x => x.Type == Claims.Permissions)?.Value.Split(',')
?? Array.Empty<string>();
}
/// <summary>
/// Get the id of the current user or null if unlogged or invalid.
/// </summary>
/// <param name="user">The user.</param>
/// <returns>The id of the user or null.</returns>
public static Guid? GetId(this ClaimsPrincipal user)
{
Claim? value = user.FindFirst(Claims.Id);
if (Guid.TryParse(value?.Value, out Guid id))
return id;
return null;
}
}
}

View File

@ -19,6 +19,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
@ -144,6 +146,17 @@ namespace Kyoo.Abstractions.Models
Hls = $"/video/movie/{Slug}/master.m3u8",
};
[SerializeIgnore] public ICollection<WatchInfo> Watched { get; set; }
/// <summary>
/// Metadata of what an user as started/planned to watch.
/// </summary>
[Projectable(UseMemberBody = nameof(_WatchInfo), OnlyOnInclude = true)]
[LoadableRelation] public WatchInfo? WatchInfo { get; set; }
// There is a global query filter to filter by user so we just need to do single.
private WatchInfo? _WatchInfo => Watched.FirstOrDefault();
/// <inheritdoc />
public void OnMerge(object merged)
{

View File

@ -69,6 +69,12 @@ namespace Kyoo.Abstractions.Models
/// </summary>
public Image? Logo { get; set; }
/// <summary>
/// The user's watch list.
/// </summary>
[SerializeIgnore]
public ICollection<WatchInfo>? Watchlist { get; set; }
public User() { }
[JsonConstructor]

View File

@ -0,0 +1,101 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models
{
/// <summary>
/// Has the user started watching, is it planned?
/// </summary>
public enum WatchStatus
{
/// <summary>
/// The user has already watched this.
/// </summary>
Completed,
/// <summary>
/// The user started watching this but has not finished.
/// </summary>
Watching,
/// <summary>
/// The user does not plan to continue watching.
/// </summary>
Droped,
/// <summary>
/// The user has not started watching this but plans to.
/// </summary>
Planned,
}
/// <summary>
/// Metadata of what an user as started/planned to watch.
/// </summary>
public class WatchInfo : IAddedDate
{
/// <summary>
/// The ID of the user that started watching this episode.
/// </summary>
[SerializeIgnore] public Guid UserId { get; set; }
/// <summary>
/// The user that started watching this episode.
/// </summary>
[SerializeIgnore] public User User { get; set; }
/// <summary>
/// The ID of the episode started.
/// </summary>
[SerializeIgnore] public Guid? EpisodeId { get; set; }
/// <summary>
/// The <see cref="Episode"/> started.
/// </summary>
[SerializeIgnore] public Episode? Episode { get; set; }
/// <summary>
/// The ID of the movie started.
/// </summary>
[SerializeIgnore] public Guid? MovieId { get; set; }
/// <summary>
/// The <see cref="Movie"/> started.
/// </summary>
[SerializeIgnore] public Movie? Movie { get; set; }
/// <inheritdoc/>
[SerializeIgnore] public DateTime AddedDate { get; set; }
/// <summary>
/// Has the user started watching, is it planned?
/// </summary>
public WatchStatus Status { get; set; }
/// <summary>
/// Where the player has stopped watching the episode (in seconds).
/// </summary>
/// <remarks>
/// Null if the status is not Watching.
/// </remarks>
public int? WatchedTime { get; set; }
}
}

View File

@ -27,6 +27,8 @@ using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ValueGeneration;
@ -42,6 +44,8 @@ namespace Kyoo.Postgresql
/// </remarks>
public abstract class DatabaseContext : DbContext
{
private readonly IHttpContextAccessor _accessor;
/// <summary>
/// Calculate the MD5 of a string, can only be used in database context.
/// </summary>
@ -49,6 +53,8 @@ namespace Kyoo.Postgresql
/// <returns>The hash</returns>
public static string MD5(string str) => throw new NotSupportedException();
public Guid? CurrentUserId => _accessor.HttpContext?.User.GetId();
/// <summary>
/// All collections of Kyoo. See <see cref="Collection"/>.
/// </summary>
@ -94,6 +100,8 @@ namespace Kyoo.Postgresql
// /// </summary>
// public DbSet<PeopleRole> PeopleRoles { get; set; }
public DbSet<WatchInfo> WatchInfo { get; set; }
/// <summary>
/// Add a many to many link between two resources.
/// </summary>
@ -114,18 +122,16 @@ namespace Kyoo.Postgresql
});
}
/// <summary>
/// The default constructor
/// </summary>
protected DatabaseContext() { }
protected DatabaseContext(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
/// <summary>
/// Create a new <see cref="DatabaseContext"/> using specific options
/// </summary>
/// <param name="options">The options to use.</param>
protected DatabaseContext(DbContextOptions options)
protected DatabaseContext(DbContextOptions options, IHttpContextAccessor accessor)
: base(options)
{ }
{
_accessor = accessor;
}
/// <summary>
/// Get the name of the link table of the two given types.
@ -296,6 +302,11 @@ namespace Kyoo.Postgresql
modelBuilder.Entity<User>().OwnsOne(x => x.Logo);
modelBuilder.Entity<WatchInfo>()
.HasKey(x => new { User = x.UserId, Episode = x.EpisodeId, Movie = x.MovieId });
modelBuilder.Entity<WatchInfo>().HasQueryFilter(x => x.UserId == CurrentUserId);
modelBuilder.Entity<Movie>().Ignore(x => x.WatchInfo);
modelBuilder.Entity<Collection>()
.HasIndex(x => x.Slug)
.IsUnique();

View File

@ -20,6 +20,8 @@ using System;
using System.Globalization;
using EFCore.NamingConventions.Internal;
using Kyoo.Abstractions.Models;
using Kyoo.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Npgsql;
@ -47,29 +49,24 @@ namespace Kyoo.Postgresql
{
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<Genre>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<WatchStatus>();
}
/// <summary>
/// A basic constructor that set default values (query tracker behaviors, mapping enums...)
/// Design time constructor (dotnet ef migrations add). Do not use
/// </summary>
public PostgresContext() { }
public PostgresContext()
: base(null!)
{ }
/// <summary>
/// Create a new <see cref="PostgresContext"/> using specific options
/// </summary>
/// <param name="options">The options to use.</param>
public PostgresContext(DbContextOptions options)
: base(options)
public PostgresContext(DbContextOptions options, IHttpContextAccessor accessor)
: base(options, accessor)
{
_skipConfigure = true;
}
/// <summary>
/// A basic constructor that set default values (query tracker behaviors, mapping enums...)
/// </summary>
/// <param name="connection">The connection string to use</param>
/// <param name="debugMode">Is this instance in debug mode?</param>
public PostgresContext(string connection, bool debugMode)
public PostgresContext(string connection, bool debugMode, IHttpContextAccessor accessor)
: base(accessor)
{
_debugMode = debugMode;
}
@ -99,6 +96,7 @@ namespace Kyoo.Postgresql
{
modelBuilder.HasPostgresEnum<Status>();
modelBuilder.HasPostgresEnum<Genre>();
modelBuilder.HasPostgresEnum<WatchStatus>();
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)
.HasTranslation(args =>