diff --git a/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs
index ca653c53..7c714322 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IWatchStatusRepository.cs
@@ -17,7 +17,6 @@
// along with Kyoo. If not, see .
using System;
-using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
@@ -26,7 +25,7 @@ namespace Kyoo.Abstractions.Controllers;
///
/// A local repository to handle watched items
///
-public interface IWatchStatusRepository
+public interface IWatchStatusRepository : IRepository
{
// ///
// /// The event handler type for all events of this repository.
diff --git a/back/src/Kyoo.Abstractions/Models/News.cs b/back/src/Kyoo.Abstractions/Models/INews.cs
similarity index 100%
rename from back/src/Kyoo.Abstractions/Models/News.cs
rename to back/src/Kyoo.Abstractions/Models/INews.cs
diff --git a/back/src/Kyoo.Abstractions/Models/IWatchlist.cs b/back/src/Kyoo.Abstractions/Models/IWatchlist.cs
new file mode 100644
index 00000000..498e2970
--- /dev/null
+++ b/back/src/Kyoo.Abstractions/Models/IWatchlist.cs
@@ -0,0 +1,31 @@
+// 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 Kyoo.Abstractions.Controllers;
+using Kyoo.Abstractions.Models.Attributes;
+
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A watch list item.
+///
+[OneOf(Types = new[] { typeof(Show), typeof(Movie), typeof(Episode) })]
+public interface IWatchlist : IResource, IThumbnails, IMetadata, IAddedDate, IQuery
+{
+ static Sort IQuery.DefaultSort => new Sort.By(nameof(AddedDate), true);
+}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
index 1d0a728e..55aae426 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
@@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models
///
/// A class to represent a single show's episode.
///
- public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews
+ public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews, IWatchlist
{
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
public static Sort DefaultSort => new Sort.Conglomerate(
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
index 819bc4fd..fff39628 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
@@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models
///
/// A series or a movie.
///
- public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, INews
+ public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, INews, IWatchlist
{
public static Sort DefaultSort => new Sort.By(x => x.Name);
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
index 8c2a6897..bef33e61 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
@@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models
///
/// A series or a movie.
///
- public class Show : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem
+ public class Show : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, IWatchlist
{
public static Sort DefaultSort => new Sort.By(x => x.Name);
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs b/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs
index c6f14bc5..80216f2d 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/WatchStatus.cs
@@ -17,9 +17,6 @@
// along with Kyoo. If not, see .
using System;
-using System.ComponentModel.DataAnnotations.Schema;
-using System.Linq;
-using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models
diff --git a/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs
index d27f3e32..a418fe30 100644
--- a/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs
+++ b/back/src/Kyoo.Core/Controllers/Repositories/WatchStatusRepository.cs
@@ -17,7 +17,10 @@
// along with Kyoo. If not, see .
using System;
+using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Data.Common;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
@@ -28,7 +31,7 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers;
-public class WatchStatusRepository : IWatchStatusRepository
+public class WatchStatusRepository : DapperRepository, IWatchStatusRepository
{
///
/// If the watch percent is below this value, don't consider the item started.
@@ -55,13 +58,64 @@ public class WatchStatusRepository : IWatchStatusRepository
public WatchStatusRepository(DatabaseContext database,
IRepository episodes,
- IRepository movies)
+ IRepository movies,
+ DbConnection db,
+ SqlVariableContext context)
+ : base(db, context)
{
_database = database;
_episodes = episodes;
_movies = movies;
}
+ // language=PostgreSQL
+ protected override FormattableString Sql => $"""
+ select
+ s.*,
+ m.*,
+ e.*
+ /* includes */
+ from (
+ select
+ s.* -- Show as s
+ from
+ shows as s
+ inner join show_watch_status as sw on sw.show_id = s.id
+ and sw.user_id = [current_user]) as s
+ full outer join (
+ select
+ m.* -- Movie as m
+ from
+ movies as m
+ inner join movie_watch_status as mw on mw.movie_id = m.id
+ and mw.user_id = [current_user]) as s) as m
+ full outer join (
+ select
+ e.* -- Episode as e
+ from
+ episode as es
+ inner join episode_watch_status as ew on ew.episode_id = e.id
+ and ew.user_id = [current_user])) as e
+ """;
+
+ protected override Dictionary Config => new()
+ {
+ { "s", typeof(Show) },
+ { "m", typeof(Movie) },
+ { "e", typeof(Episode) },
+ };
+
+ protected override IWatchlist Mapper(List