From 4135fc5703b9fc6eeb8e6947f5cd2b7665b52a33 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 12 Nov 2023 17:15:41 +0100 Subject: [PATCH] Start watchlist implementations --- .../Extensions.cs | 13 +++ .../Models/Resources/Movie.cs | 13 +++ .../Models/Resources/User.cs | 6 ++ .../Models/Resources/WatchInfo.cs | 101 ++++++++++++++++++ .../Models/Utils}/Claims.cs | 0 back/src/Kyoo.Postgresql/DatabaseContext.cs | 31 ++++-- back/src/Kyoo.Postgresql/PostgresContext.cs | 26 +++-- 7 files changed, 166 insertions(+), 24 deletions(-) rename back/src/{Kyoo.Authentication => Kyoo.Abstractions}/Extensions.cs (78%) create mode 100644 back/src/Kyoo.Abstractions/Models/Resources/WatchInfo.cs rename back/src/{Kyoo.Authentication/Models => Kyoo.Abstractions/Models/Utils}/Claims.cs (100%) diff --git a/back/src/Kyoo.Authentication/Extensions.cs b/back/src/Kyoo.Abstractions/Extensions.cs similarity index 78% rename from back/src/Kyoo.Authentication/Extensions.cs rename to back/src/Kyoo.Abstractions/Extensions.cs index 8062b2e8..1d4a26c2 100644 --- a/back/src/Kyoo.Authentication/Extensions.cs +++ b/back/src/Kyoo.Abstractions/Extensions.cs @@ -39,5 +39,18 @@ namespace Kyoo.Authentication return user.Claims.FirstOrDefault(x => x.Type == Claims.Permissions)?.Value.Split(',') ?? Array.Empty(); } + + /// + /// Get the id of the current user or null if unlogged or invalid. + /// + /// The user. + /// The id of the user or null. + 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; + } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index fc881351..8723d73f 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -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 Watched { get; set; } + + /// + /// Metadata of what an user as started/planned to watch. + /// + [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(); + /// public void OnMerge(object merged) { diff --git a/back/src/Kyoo.Abstractions/Models/Resources/User.cs b/back/src/Kyoo.Abstractions/Models/Resources/User.cs index 0d9b90a0..5328f381 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/User.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/User.cs @@ -69,6 +69,12 @@ namespace Kyoo.Abstractions.Models /// public Image? Logo { get; set; } + /// + /// The user's watch list. + /// + [SerializeIgnore] + public ICollection? Watchlist { get; set; } + public User() { } [JsonConstructor] diff --git a/back/src/Kyoo.Abstractions/Models/Resources/WatchInfo.cs b/back/src/Kyoo.Abstractions/Models/Resources/WatchInfo.cs new file mode 100644 index 00000000..5a1a2834 --- /dev/null +++ b/back/src/Kyoo.Abstractions/Models/Resources/WatchInfo.cs @@ -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 . + +using System; +using Kyoo.Abstractions.Models.Attributes; + +namespace Kyoo.Abstractions.Models +{ + /// + /// Has the user started watching, is it planned? + /// + public enum WatchStatus + { + /// + /// The user has already watched this. + /// + Completed, + + /// + /// The user started watching this but has not finished. + /// + Watching, + + /// + /// The user does not plan to continue watching. + /// + Droped, + + /// + /// The user has not started watching this but plans to. + /// + Planned, + } + + /// + /// Metadata of what an user as started/planned to watch. + /// + public class WatchInfo : IAddedDate + { + /// + /// The ID of the user that started watching this episode. + /// + [SerializeIgnore] public Guid UserId { get; set; } + + /// + /// The user that started watching this episode. + /// + [SerializeIgnore] public User User { get; set; } + + /// + /// The ID of the episode started. + /// + [SerializeIgnore] public Guid? EpisodeId { get; set; } + + /// + /// The started. + /// + [SerializeIgnore] public Episode? Episode { get; set; } + + /// + /// The ID of the movie started. + /// + [SerializeIgnore] public Guid? MovieId { get; set; } + + /// + /// The started. + /// + [SerializeIgnore] public Movie? Movie { get; set; } + + /// + [SerializeIgnore] public DateTime AddedDate { get; set; } + + /// + /// Has the user started watching, is it planned? + /// + public WatchStatus Status { get; set; } + + /// + /// Where the player has stopped watching the episode (in seconds). + /// + /// + /// Null if the status is not Watching. + /// + public int? WatchedTime { get; set; } + } +} diff --git a/back/src/Kyoo.Authentication/Models/Claims.cs b/back/src/Kyoo.Abstractions/Models/Utils/Claims.cs similarity index 100% rename from back/src/Kyoo.Authentication/Models/Claims.cs rename to back/src/Kyoo.Abstractions/Models/Utils/Claims.cs diff --git a/back/src/Kyoo.Postgresql/DatabaseContext.cs b/back/src/Kyoo.Postgresql/DatabaseContext.cs index ecca45d7..f9d097b4 100644 --- a/back/src/Kyoo.Postgresql/DatabaseContext.cs +++ b/back/src/Kyoo.Postgresql/DatabaseContext.cs @@ -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 /// public abstract class DatabaseContext : DbContext { + private readonly IHttpContextAccessor _accessor; + /// /// Calculate the MD5 of a string, can only be used in database context. /// @@ -49,6 +53,8 @@ namespace Kyoo.Postgresql /// The hash public static string MD5(string str) => throw new NotSupportedException(); + public Guid? CurrentUserId => _accessor.HttpContext?.User.GetId(); + /// /// All collections of Kyoo. See . /// @@ -94,6 +100,8 @@ namespace Kyoo.Postgresql // /// // public DbSet PeopleRoles { get; set; } + public DbSet WatchInfo { get; set; } + /// /// Add a many to many link between two resources. /// @@ -114,18 +122,16 @@ namespace Kyoo.Postgresql }); } - /// - /// The default constructor - /// - protected DatabaseContext() { } + protected DatabaseContext(IHttpContextAccessor accessor) + { + _accessor = accessor; + } - /// - /// Create a new using specific options - /// - /// The options to use. - protected DatabaseContext(DbContextOptions options) + protected DatabaseContext(DbContextOptions options, IHttpContextAccessor accessor) : base(options) - { } + { + _accessor = accessor; + } /// /// Get the name of the link table of the two given types. @@ -296,6 +302,11 @@ namespace Kyoo.Postgresql modelBuilder.Entity().OwnsOne(x => x.Logo); + modelBuilder.Entity() + .HasKey(x => new { User = x.UserId, Episode = x.EpisodeId, Movie = x.MovieId }); + modelBuilder.Entity().HasQueryFilter(x => x.UserId == CurrentUserId); + modelBuilder.Entity().Ignore(x => x.WatchInfo); + modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); diff --git a/back/src/Kyoo.Postgresql/PostgresContext.cs b/back/src/Kyoo.Postgresql/PostgresContext.cs index 009f9edc..2ec33d15 100644 --- a/back/src/Kyoo.Postgresql/PostgresContext.cs +++ b/back/src/Kyoo.Postgresql/PostgresContext.cs @@ -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(); NpgsqlConnection.GlobalTypeMapper.MapEnum(); + NpgsqlConnection.GlobalTypeMapper.MapEnum(); } /// - /// A basic constructor that set default values (query tracker behaviors, mapping enums...) + /// Design time constructor (dotnet ef migrations add). Do not use /// - public PostgresContext() { } + public PostgresContext() + : base(null!) + { } - /// - /// Create a new using specific options - /// - /// The options to use. - public PostgresContext(DbContextOptions options) - : base(options) + public PostgresContext(DbContextOptions options, IHttpContextAccessor accessor) + : base(options, accessor) { _skipConfigure = true; } - /// - /// A basic constructor that set default values (query tracker behaviors, mapping enums...) - /// - /// The connection string to use - /// Is this instance in debug mode? - 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(); modelBuilder.HasPostgresEnum(); + modelBuilder.HasPostgresEnum(); modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!) .HasTranslation(args =>