From c942794b89a21f853f81b34b8e2129568090c723 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 17:52:25 +0100 Subject: [PATCH 01/15] Add autosync to docker builds --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 392c282d..2ff23a95 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -24,6 +24,9 @@ jobs: - context: ./scanner label: scanner image: zoriya/kyoo_scanner + - context: ./autosync + label: autosync + image: zoriya/kyoo_autosync - context: ./transcoder label: transcoder image: zoriya/kyoo_transcoder From 8afbc63b85df2a25a3b0002d1706e816d203aac0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 18:52:54 +0100 Subject: [PATCH 02/15] Use DateOnly for dates fields instead of DateTime --- .../Models/Resources/Episode.cs | 2 +- .../Models/Resources/Movie.cs | 6 +- .../Models/Resources/Season.cs | 4 +- .../Models/Resources/Show.cs | 6 +- .../20240324174638_UseDateOnly.Designer.cs | 1317 +++++++++++++++++ .../Migrations/20240324174638_UseDateOnly.cs | 181 +++ .../PostgresContextModelSnapshot.cs | 32 +- 7 files changed, 1525 insertions(+), 23 deletions(-) create mode 100644 back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.Designer.cs create mode 100644 back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.cs diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 75fe4746..536decaf 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -151,7 +151,7 @@ public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, IN /// /// The release date of this episode. It can be null if unknown. /// - public DateTime? ReleaseDate { get; set; } + public DateOnly? ReleaseDate { get; set; } /// public DateTime AddedDate { get; set; } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs index 3223c5e8..e70371b1 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs @@ -104,7 +104,7 @@ public class Movie /// /// The date this movie aired. /// - public DateTime? AirDate { get; set; } + public DateOnly? AirDate { get; set; } /// public DateTime AddedDate { get; set; } @@ -120,11 +120,11 @@ public class Movie [JsonIgnore] [Column("air_date")] - public DateTime? StartAir => AirDate; + public DateOnly? StartAir => AirDate; [JsonIgnore] [Column("air_date")] - public DateTime? EndAir => AirDate; + public DateOnly? EndAir => AirDate; /// /// A video of a few minutes that tease the content. diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs index e88680a9..8d3e0489 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -97,7 +97,7 @@ public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate /// /// The starting air date of this season. /// - public DateTime? StartDate { get; set; } + public DateOnly? StartDate { get; set; } /// public DateTime AddedDate { get; set; } @@ -105,7 +105,7 @@ public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate /// /// The ending date of this season. /// - public DateTime? EndDate { get; set; } + public DateOnly? EndDate { get; set; } /// public Image? Poster { get; set; } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs index 6967a1e7..edee8866 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs @@ -94,13 +94,13 @@ public class Show /// /// The date this show started airing. It can be null if this is unknown. /// - public DateTime? StartAir { get; set; } + public DateOnly? StartAir { get; set; } /// /// The date this show finished airing. /// It can also be null if this is unknown. /// - public DateTime? EndAir { get; set; } + public DateOnly? EndAir { get; set; } /// public DateTime AddedDate { get; set; } @@ -121,7 +121,7 @@ public class Show [JsonIgnore] [Column("start_air")] - public DateTime? AirDate => StartAir; + public DateOnly? AirDate => StartAir; /// public Dictionary ExternalId { get; set; } = new(); diff --git a/back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.Designer.cs b/back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.Designer.cs new file mode 100644 index 00000000..b8e4163f --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.Designer.cs @@ -0,0 +1,1317 @@ +// +using System; +using System.Collections.Generic; +using Kyoo.Abstractions.Models; +using Kyoo.Postgresql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Kyoo.Postgresql.Migrations +{ + [DbContext(typeof(PostgresContext))] + [Migration("20240324174638_UseDateOnly")] + partial class UseDateOnly + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "watch_status", new[] { "completed", "watching", "droped", "planned", "deleted" }); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_collections"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_collections_slug"); + + b.ToTable("collections", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AbsoluteNumber") + .HasColumnType("integer") + .HasColumnName("absolute_number"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("EpisodeNumber") + .HasColumnType("integer") + .HasColumnName("episode_number"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("ReleaseDate") + .HasColumnType("date") + .HasColumnName("release_date"); + + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + + b.Property("SeasonId") + .HasColumnType("uuid") + .HasColumnName("season_id"); + + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); + + b.Property("ShowId") + .HasColumnType("uuid") + .HasColumnName("show_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_episodes"); + + b.HasIndex("SeasonId") + .HasDatabaseName("ix_episodes_season_id"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_episodes_slug"); + + b.HasIndex("ShowId", "SeasonNumber", "EpisodeNumber", "AbsoluteNumber") + .IsUnique() + .HasDatabaseName("ix_episodes_show_id_season_number_episode_number_absolute_numb"); + + b.ToTable("episodes", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.EpisodeWatchStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("EpisodeId") + .HasColumnType("uuid") + .HasColumnName("episode_id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("PlayedDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("played_date"); + + b.Property("Status") + .HasColumnType("watch_status") + .HasColumnName("status"); + + b.Property("WatchedPercent") + .HasColumnType("integer") + .HasColumnName("watched_percent"); + + b.Property("WatchedTime") + .HasColumnType("integer") + .HasColumnName("watched_time"); + + b.HasKey("UserId", "EpisodeId") + .HasName("pk_episode_watch_status"); + + b.HasIndex("EpisodeId") + .HasDatabaseName("ix_episode_watch_status_episode_id"); + + b.ToTable("episode_watch_status", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Issue", b => + { + b.Property("Domain") + .HasColumnType("text") + .HasColumnName("domain"); + + b.Property("Cause") + .HasColumnType("text") + .HasColumnName("cause"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("json") + .HasColumnName("extra"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.HasKey("Domain", "Cause") + .HasName("pk_issues"); + + b.ToTable("issues", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("AirDate") + .HasColumnType("date") + .HasColumnName("air_date"); + + b.Property("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Rating") + .HasColumnType("integer") + .HasColumnName("rating"); + + b.Property("Runtime") + .HasColumnType("integer") + .HasColumnName("runtime"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("StudioId") + .HasColumnType("uuid") + .HasColumnName("studio_id"); + + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); + + b.HasKey("Id") + .HasName("pk_movies"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_movies_slug"); + + b.HasIndex("StudioId") + .HasDatabaseName("ix_movies_studio_id"); + + b.ToTable("movies", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.MovieWatchStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("MovieId") + .HasColumnType("uuid") + .HasColumnName("movie_id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("PlayedDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("played_date"); + + b.Property("Status") + .HasColumnType("watch_status") + .HasColumnName("status"); + + b.Property("WatchedPercent") + .HasColumnType("integer") + .HasColumnName("watched_percent"); + + b.Property("WatchedTime") + .HasColumnType("integer") + .HasColumnName("watched_time"); + + b.HasKey("UserId", "MovieId") + .HasName("pk_movie_watch_status"); + + b.HasIndex("MovieId") + .HasDatabaseName("ix_movie_watch_status_movie_id"); + + b.ToTable("movie_watch_status", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("EndDate") + .HasColumnType("date") + .HasColumnName("end_date"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("SeasonNumber") + .HasColumnType("integer") + .HasColumnName("season_number"); + + b.Property("ShowId") + .HasColumnType("uuid") + .HasColumnName("show_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("StartDate") + .HasColumnType("date") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("pk_seasons"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_seasons_slug"); + + b.HasIndex("ShowId", "SeasonNumber") + .IsUnique() + .HasDatabaseName("ix_seasons_show_id_season_number"); + + b.ToTable("seasons", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property>("Aliases") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("aliases"); + + b.Property("EndAir") + .HasColumnType("date") + .HasColumnName("end_air"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property>("Genres") + .IsRequired() + .HasColumnType("genre[]") + .HasColumnName("genres"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Overview") + .HasColumnType("text") + .HasColumnName("overview"); + + b.Property("Rating") + .HasColumnType("integer") + .HasColumnName("rating"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("StartAir") + .HasColumnType("date") + .HasColumnName("start_air"); + + b.Property("Status") + .HasColumnType("status") + .HasColumnName("status"); + + b.Property("StudioId") + .HasColumnType("uuid") + .HasColumnName("studio_id"); + + b.Property("Tagline") + .HasColumnType("text") + .HasColumnName("tagline"); + + b.Property>("Tags") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("tags"); + + b.Property("Trailer") + .HasColumnType("text") + .HasColumnName("trailer"); + + b.HasKey("Id") + .HasName("pk_shows"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_shows_slug"); + + b.HasIndex("StudioId") + .HasDatabaseName("ix_shows_studio_id"); + + b.ToTable("shows", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.ShowWatchStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("ShowId") + .HasColumnType("uuid") + .HasColumnName("show_id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("NextEpisodeId") + .HasColumnType("uuid") + .HasColumnName("next_episode_id"); + + b.Property("PlayedDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("played_date"); + + b.Property("Status") + .HasColumnType("watch_status") + .HasColumnName("status"); + + b.Property("UnseenEpisodesCount") + .HasColumnType("integer") + .HasColumnName("unseen_episodes_count"); + + b.Property("WatchedPercent") + .HasColumnType("integer") + .HasColumnName("watched_percent"); + + b.Property("WatchedTime") + .HasColumnType("integer") + .HasColumnName("watched_time"); + + b.HasKey("UserId", "ShowId") + .HasName("pk_show_watch_status"); + + b.HasIndex("NextEpisodeId") + .HasDatabaseName("ix_show_watch_status_next_episode_id"); + + b.HasIndex("ShowId") + .HasDatabaseName("ix_show_watch_status_show_id"); + + b.ToTable("show_watch_status", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_studios"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_studios_slug"); + + b.ToTable("studios", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AddedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("added_date") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("json") + .HasColumnName("external_id"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("text[]") + .HasColumnName("permissions"); + + b.Property("Settings") + .IsRequired() + .HasColumnType("json") + .HasColumnName("settings"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("slug"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ix_users_slug"); + + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("ix_users_username"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("link_collection_movie", b => + { + b.Property("collection_id") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("movie_id") + .HasColumnType("uuid") + .HasColumnName("movie_id"); + + b.HasKey("collection_id", "movie_id") + .HasName("pk_link_collection_movie"); + + b.HasIndex("movie_id") + .HasDatabaseName("ix_link_collection_movie_movie_id"); + + b.ToTable("link_collection_movie", (string)null); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.Property("collection_id") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("show_id") + .HasColumnType("uuid") + .HasColumnName("show_id"); + + b.HasKey("collection_id", "show_id") + .HasName("pk_link_collection_show"); + + b.HasIndex("show_id") + .HasDatabaseName("ix_link_collection_show_show_id"); + + b.ToTable("link_collection_show", (string)null); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => + { + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("CollectionId"); + + b1.ToTable("collections"); + + b1.WithOwner() + .HasForeignKey("CollectionId") + .HasConstraintName("fk_collections_collections_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("CollectionId"); + + b1.ToTable("collections"); + + b1.WithOwner() + .HasForeignKey("CollectionId") + .HasConstraintName("fk_collections_collections_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("CollectionId"); + + b1.ToTable("collections"); + + b1.WithOwner() + .HasForeignKey("CollectionId") + .HasConstraintName("fk_collections_collections_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => + { + b.HasOne("Kyoo.Abstractions.Models.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_episodes_seasons_season_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("Episodes") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_episodes_shows_show_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("EpisodeId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("EpisodeId"); + + b1.ToTable("episodes"); + + b1.WithOwner() + .HasForeignKey("EpisodeId") + .HasConstraintName("fk_episodes_episodes_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("EpisodeId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("EpisodeId"); + + b1.ToTable("episodes"); + + b1.WithOwner() + .HasForeignKey("EpisodeId") + .HasConstraintName("fk_episodes_episodes_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("EpisodeId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("EpisodeId"); + + b1.ToTable("episodes"); + + b1.WithOwner() + .HasForeignKey("EpisodeId") + .HasConstraintName("fk_episodes_episodes_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Season"); + + b.Navigation("Show"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.EpisodeWatchStatus", b => + { + b.HasOne("Kyoo.Abstractions.Models.Episode", "Episode") + .WithMany("Watched") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_episode_watch_status_episodes_episode_id"); + + b.HasOne("Kyoo.Abstractions.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_episode_watch_status_users_user_id"); + + b.Navigation("Episode"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => + { + b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") + .WithMany("Movies") + .HasForeignKey("StudioId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_movies_studios_studio_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("MovieId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("MovieId"); + + b1.ToTable("movies"); + + b1.WithOwner() + .HasForeignKey("MovieId") + .HasConstraintName("fk_movies_movies_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("MovieId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("MovieId"); + + b1.ToTable("movies"); + + b1.WithOwner() + .HasForeignKey("MovieId") + .HasConstraintName("fk_movies_movies_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("MovieId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("MovieId"); + + b1.ToTable("movies"); + + b1.WithOwner() + .HasForeignKey("MovieId") + .HasConstraintName("fk_movies_movies_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Studio"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.MovieWatchStatus", b => + { + b.HasOne("Kyoo.Abstractions.Models.Movie", "Movie") + .WithMany("Watched") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_movie_watch_status_movies_movie_id"); + + b.HasOne("Kyoo.Abstractions.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_movie_watch_status_users_user_id"); + + b.Navigation("Movie"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_seasons_shows_show_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("SeasonId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("SeasonId"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonId") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("SeasonId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("SeasonId"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonId") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("SeasonId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("SeasonId"); + + b1.ToTable("seasons"); + + b1.WithOwner() + .HasForeignKey("SeasonId") + .HasConstraintName("fk_seasons_seasons_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Show"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio") + .WithMany("Shows") + .HasForeignKey("StudioId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_shows_studios_studio_id"); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => + { + b1.Property("ShowId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("logo_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("logo_source"); + + b1.HasKey("ShowId"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowId") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => + { + b1.Property("ShowId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("poster_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("poster_source"); + + b1.HasKey("ShowId"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowId") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => + { + b1.Property("ShowId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Blurhash") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("thumbnail_blurhash"); + + b1.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("thumbnail_source"); + + b1.HasKey("ShowId"); + + b1.ToTable("shows"); + + b1.WithOwner() + .HasForeignKey("ShowId") + .HasConstraintName("fk_shows_shows_id"); + }); + + b.Navigation("Logo"); + + b.Navigation("Poster"); + + b.Navigation("Studio"); + + b.Navigation("Thumbnail"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.ShowWatchStatus", b => + { + b.HasOne("Kyoo.Abstractions.Models.Episode", "NextEpisode") + .WithMany() + .HasForeignKey("NextEpisodeId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_show_watch_status_episodes_next_episode_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", "Show") + .WithMany("Watched") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_show_watch_status_shows_show_id"); + + b.HasOne("Kyoo.Abstractions.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_show_watch_status_users_user_id"); + + b.Navigation("NextEpisode"); + + b.Navigation("Show"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("link_collection_movie", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Movie", null) + .WithMany() + .HasForeignKey("movie_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_movie_movies_movie_id"); + }); + + modelBuilder.Entity("link_collection_show", b => + { + b.HasOne("Kyoo.Abstractions.Models.Collection", null) + .WithMany() + .HasForeignKey("collection_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_show_collections_collection_id"); + + b.HasOne("Kyoo.Abstractions.Models.Show", null) + .WithMany() + .HasForeignKey("show_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_link_collection_show_shows_show_id"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Episode", b => + { + b.Navigation("Watched"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b => + { + b.Navigation("Watched"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Show", b => + { + b.Navigation("Episodes"); + + b.Navigation("Seasons"); + + b.Navigation("Watched"); + }); + + modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => + { + b.Navigation("Movies"); + + b.Navigation("Shows"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.cs b/back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.cs new file mode 100644 index 00000000..725268a0 --- /dev/null +++ b/back/src/Kyoo.Postgresql/Migrations/20240324174638_UseDateOnly.cs @@ -0,0 +1,181 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Kyoo.Postgresql.Migrations +{ + /// + public partial class UseDateOnly : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder + .AlterDatabase() + .Annotation( + "Npgsql:Enum:genre", + "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western" + ) + .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned") + .Annotation("Npgsql:Enum:watch_status", "completed,watching,droped,planned,deleted") + .OldAnnotation( + "Npgsql:Enum:genre", + "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western" + ) + .OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned") + .OldAnnotation("Npgsql:Enum:watch_status", "completed,watching,droped,planned"); + + migrationBuilder.AlterColumn( + name: "start_air", + table: "shows", + type: "date", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "end_air", + table: "shows", + type: "date", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "start_date", + table: "seasons", + type: "date", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "end_date", + table: "seasons", + type: "date", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "air_date", + table: "movies", + type: "date", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "release_date", + table: "episodes", + type: "date", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true + ); + + migrationBuilder.CreateIndex( + name: "ix_users_username", + table: "users", + column: "username", + unique: true + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex(name: "ix_users_username", table: "users"); + + migrationBuilder + .AlterDatabase() + .Annotation( + "Npgsql:Enum:genre", + "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western" + ) + .Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned") + .Annotation("Npgsql:Enum:watch_status", "completed,watching,droped,planned") + .OldAnnotation( + "Npgsql:Enum:genre", + "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western" + ) + .OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned") + .OldAnnotation( + "Npgsql:Enum:watch_status", + "completed,watching,droped,planned,deleted" + ); + + migrationBuilder.AlterColumn( + name: "start_air", + table: "shows", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateOnly), + oldType: "date", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "end_air", + table: "shows", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateOnly), + oldType: "date", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "start_date", + table: "seasons", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateOnly), + oldType: "date", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "end_date", + table: "seasons", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateOnly), + oldType: "date", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "air_date", + table: "movies", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateOnly), + oldType: "date", + oldNullable: true + ); + + migrationBuilder.AlterColumn( + name: "release_date", + table: "episodes", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateOnly), + oldType: "date", + oldNullable: true + ); + } + } +} diff --git a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs index 68327e96..0a1e4337 100644 --- a/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs +++ b/back/src/Kyoo.Postgresql/Migrations/PostgresContextModelSnapshot.cs @@ -19,12 +19,12 @@ namespace Kyoo.Postgresql.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.12") + .HasAnnotation("ProductVersion", "8.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); - NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "watch_status", new[] { "completed", "watching", "droped", "planned" }); + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "watch_status", new[] { "completed", "watching", "droped", "planned", "deleted" }); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => @@ -109,8 +109,8 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text") .HasColumnName("path"); - b.Property("ReleaseDate") - .HasColumnType("timestamp with time zone") + b.Property("ReleaseDate") + .HasColumnType("date") .HasColumnName("release_date"); b.Property("Runtime") @@ -238,8 +238,8 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("added_date") .HasDefaultValueSql("now() at time zone 'utc'"); - b.Property("AirDate") - .HasColumnType("timestamp with time zone") + b.Property("AirDate") + .HasColumnType("date") .HasColumnName("air_date"); b.Property("Aliases") @@ -373,8 +373,8 @@ namespace Kyoo.Postgresql.Migrations .HasColumnName("added_date") .HasDefaultValueSql("now() at time zone 'utc'"); - b.Property("EndDate") - .HasColumnType("timestamp with time zone") + b.Property("EndDate") + .HasColumnType("date") .HasColumnName("end_date"); b.Property("ExternalId") @@ -404,8 +404,8 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.Property("StartDate") - .HasColumnType("timestamp with time zone") + b.Property("StartDate") + .HasColumnType("date") .HasColumnName("start_date"); b.HasKey("Id") @@ -440,8 +440,8 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("text[]") .HasColumnName("aliases"); - b.Property("EndAir") - .HasColumnType("timestamp with time zone") + b.Property("EndAir") + .HasColumnType("date") .HasColumnName("end_air"); b.Property("ExternalId") @@ -473,8 +473,8 @@ namespace Kyoo.Postgresql.Migrations .HasColumnType("character varying(256)") .HasColumnName("slug"); - b.Property("StartAir") - .HasColumnType("timestamp with time zone") + b.Property("StartAir") + .HasColumnType("date") .HasColumnName("start_air"); b.Property("Status") @@ -651,6 +651,10 @@ namespace Kyoo.Postgresql.Migrations .IsUnique() .HasDatabaseName("ix_users_slug"); + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("ix_users_username"); + b.ToTable("users", (string)null); }); From dc7f7feab1bd047bee3b05d937936e7cad24fa62 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 18:56:34 +0100 Subject: [PATCH 03/15] Mark migrations as generated --- .gitattributes | 64 +------------------------------------------------- 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/.gitattributes b/.gitattributes index 1ff0c423..9097bb0f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,63 +1 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### -* text=auto - -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp - -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary - -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary - -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain +*.Designer.cs linguist-generated From 64eb70d2928509b7145f7a87022c8ab6b64e23e0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 20:42:02 +0100 Subject: [PATCH 04/15] Update dapper for DateOnly support --- back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj | 2 +- back/src/Kyoo.Core/Kyoo.Core.csproj | 2 +- back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj b/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj index fca77439..cd68fd32 100644 --- a/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj +++ b/back/src/Kyoo.Abstractions/Kyoo.Abstractions.csproj @@ -8,7 +8,7 @@ - + diff --git a/back/src/Kyoo.Core/Kyoo.Core.csproj b/back/src/Kyoo.Core/Kyoo.Core.csproj index ed7121e3..67b5c30d 100644 --- a/back/src/Kyoo.Core/Kyoo.Core.csproj +++ b/back/src/Kyoo.Core/Kyoo.Core.csproj @@ -8,7 +8,7 @@ - + diff --git a/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj b/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj index b1a5902d..807f357a 100644 --- a/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj +++ b/back/src/Kyoo.Postgresql/Kyoo.Postgresql.csproj @@ -6,7 +6,7 @@ - + From fee6032e7860ad9ce7e3abc2dd8931c8ba08be3c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 20:47:56 +0100 Subject: [PATCH 05/15] Fix images from string conversion used by the scanner --- .../Utility/TypeConverterJsonAdapter.cs | 67 +++++++++++++++++++ back/src/Kyoo.Abstractions/Utility/Utility.cs | 2 +- back/src/Kyoo.Core/CoreModule.cs | 1 + 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs diff --git a/back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs b/back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs new file mode 100644 index 00000000..d7736648 --- /dev/null +++ b/back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs @@ -0,0 +1,67 @@ +// 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 System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Kyoo.Abstractions.Models; +using Kyoo.Abstractions.Models.Attributes; +using Microsoft.AspNetCore.Http; +using static System.Text.Json.JsonNamingPolicy; + +namespace Kyoo.Utils; + + +public class TypeConverterJsonAdapter : JsonConverter +{ + public override object Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + TypeConverter converter = TypeDescriptor.GetConverter(typeToConvert); + string? text = reader.GetString(); + return converter.ConvertFromString(text); + } + + public override void Write( + Utf8JsonWriter writer, + object objectToWrite, + JsonSerializerOptions options + ) + { + var converter = TypeDescriptor.GetConverter(objectToWrite); + var text = converter.ConvertToString(objectToWrite); + writer.WriteStringValue(text); + } + + public override bool CanConvert(Type typeToConvert) + { + var hasConverter = typeToConvert + .GetCustomAttributes(inherit: true) + .Any(); + return hasConverter; + } +} diff --git a/back/src/Kyoo.Abstractions/Utility/Utility.cs b/back/src/Kyoo.Abstractions/Utility/Utility.cs index 422d673d..3bd800e4 100644 --- a/back/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/back/src/Kyoo.Abstractions/Utility/Utility.cs @@ -38,7 +38,7 @@ public static class Utility new() { TypeInfoResolver = new JsonKindResolver(), - Converters = { new JsonStringEnumConverter() }, + Converters = { new JsonStringEnumConverter(), new TypeConverterJsonAdapter() }, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs index 8d683b80..da6759dd 100644 --- a/back/src/Kyoo.Core/CoreModule.cs +++ b/back/src/Kyoo.Core/CoreModule.cs @@ -101,6 +101,7 @@ public class CoreModule : IPlugin Modifiers = { IncludeBinder.HandleLoadableFields } }; x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + x.JsonSerializerOptions.Converters.Add(new TypeConverterJsonAdapter()); x.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }) .AddDataAnnotations() From be8bf53cc6274130598aaf1901d534f314964802 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 22:07:29 +0100 Subject: [PATCH 06/15] Use custom json convertor for images instead of relying on type convertor --- .../Resources/Interfaces/IThumbnails.cs | 39 +++++------ .../Utility/TypeConverterJsonAdapter.cs | 67 ------------------- back/src/Kyoo.Abstractions/Utility/Utility.cs | 2 +- back/src/Kyoo.Core/CoreModule.cs | 1 - 4 files changed, 21 insertions(+), 88 deletions(-) delete mode 100644 back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs index 459f02a0..5095dfe4 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs @@ -20,6 +20,7 @@ using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Globalization; +using System.Text.Json; using System.Text.Json.Serialization; using Kyoo.Abstractions.Models.Attributes; @@ -47,7 +48,7 @@ public interface IThumbnails public Image? Logo { get; set; } } -[TypeConverter(typeof(ImageConvertor))] +[JsonConverter(typeof(ImageConvertor))] [SqlFirstColumn(nameof(Source))] public class Image { @@ -71,32 +72,32 @@ public class Image Blurhash = blurhash ?? "000000"; } - public class ImageConvertor : TypeConverter + public class ImageConvertor : JsonConverter { /// - public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) - { - if (sourceType == typeof(string)) - return true; - return base.CanConvertFrom(context, sourceType); - } - - /// - public override object ConvertFrom( - ITypeDescriptorContext? context, - CultureInfo? culture, - object value + public override Image? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options ) { - if (value is not string source) - return base.ConvertFrom(context, culture, value)!; - return new Image(source); + if (reader.TokenType == JsonTokenType.String && reader.GetString() is string source) + return new Image(source); + using JsonDocument document = JsonDocument.ParseValue(ref reader); + return document.RootElement.Deserialize(); } /// - public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + public override void Write( + Utf8JsonWriter writer, + Image value, + JsonSerializerOptions options + ) { - return false; + writer.WriteStartObject(); + writer.WriteString("source", value.Source); + writer.WriteString("blurhash", value.Blurhash); + writer.WriteEndObject(); } } } diff --git a/back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs b/back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs deleted file mode 100644 index d7736648..00000000 --- a/back/src/Kyoo.Abstractions/Utility/TypeConverterJsonAdapter.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Attributes; -using Microsoft.AspNetCore.Http; -using static System.Text.Json.JsonNamingPolicy; - -namespace Kyoo.Utils; - - -public class TypeConverterJsonAdapter : JsonConverter -{ - public override object Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options - ) - { - TypeConverter converter = TypeDescriptor.GetConverter(typeToConvert); - string? text = reader.GetString(); - return converter.ConvertFromString(text); - } - - public override void Write( - Utf8JsonWriter writer, - object objectToWrite, - JsonSerializerOptions options - ) - { - var converter = TypeDescriptor.GetConverter(objectToWrite); - var text = converter.ConvertToString(objectToWrite); - writer.WriteStringValue(text); - } - - public override bool CanConvert(Type typeToConvert) - { - var hasConverter = typeToConvert - .GetCustomAttributes(inherit: true) - .Any(); - return hasConverter; - } -} diff --git a/back/src/Kyoo.Abstractions/Utility/Utility.cs b/back/src/Kyoo.Abstractions/Utility/Utility.cs index 3bd800e4..422d673d 100644 --- a/back/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/back/src/Kyoo.Abstractions/Utility/Utility.cs @@ -38,7 +38,7 @@ public static class Utility new() { TypeInfoResolver = new JsonKindResolver(), - Converters = { new JsonStringEnumConverter(), new TypeConverterJsonAdapter() }, + Converters = { new JsonStringEnumConverter() }, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs index da6759dd..8d683b80 100644 --- a/back/src/Kyoo.Core/CoreModule.cs +++ b/back/src/Kyoo.Core/CoreModule.cs @@ -101,7 +101,6 @@ public class CoreModule : IPlugin Modifiers = { IncludeBinder.HandleLoadableFields } }; x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - x.JsonSerializerOptions.Converters.Add(new TypeConverterJsonAdapter()); x.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }) .AddDataAnnotations() From b08bfeceb6f7ab20de3a6998d460ab97dda6d722 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 22:23:04 +0100 Subject: [PATCH 07/15] Fix race condition on info retrival --- transcoder/src/info.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/transcoder/src/info.go b/transcoder/src/info.go index 2a3d1247..de8413a5 100644 --- a/transcoder/src/info.go +++ b/transcoder/src/info.go @@ -9,13 +9,12 @@ import ( "path/filepath" "strconv" "strings" + "sync" "github.com/zoriya/go-mediainfo" ) type MediaInfo struct { - // closed if the mediainfo is ready for read. open otherwise - ready <-chan struct{} // The sha1 of the video file. Sha string `json:"sha"` /// The internal path of the video file. @@ -177,37 +176,38 @@ var SubtitleExtensions = map[string]string{ "vtt": "vtt", } -var infos = NewCMap[string, *MediaInfo]() +type MICache struct { + info *MediaInfo + ready sync.WaitGroup +} + +var infos = NewCMap[string, *MICache]() func GetInfo(path string, sha string, route string) (*MediaInfo, error) { var err error - ret, _ := infos.GetOrCreate(sha, func() *MediaInfo { - readyChan := make(chan struct{}) - mi := &MediaInfo{ - Sha: sha, - ready: readyChan, - } + ret, _ := infos.GetOrCreate(sha, func() *MICache { + mi := &MICache{info: &MediaInfo{Sha: sha}} + mi.ready.Add(1) go func() { save_path := fmt.Sprintf("%s/%s/info.json", Settings.Metadata, sha) - if err := getSavedInfo(save_path, mi); err == nil { + if err := getSavedInfo(save_path, mi.info); err == nil { log.Printf("Using mediainfo cache on filesystem for %s", path) - close(readyChan) + mi.ready.Done() return } var val *MediaInfo val, err = getInfo(path, route) - *mi = *val - mi.ready = readyChan - mi.Sha = sha - close(readyChan) - saveInfo(save_path, mi) + *mi.info = *val + mi.info.Sha = sha + mi.ready.Done() + saveInfo(save_path, mi.info) }() return mi }) - <-ret.ready - return ret, err + ret.ready.Wait() + return ret.info, err } func getSavedInfo[T any](save_path string, mi *T) error { From 44c88a885f84b1aa77324c37ec94ede05561f61c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 24 Mar 2024 23:01:17 +0100 Subject: [PATCH 08/15] Handle invalid users on mobile too --- front/apps/web/src/pages/_app.tsx | 14 ++------ front/packages/models/src/accounts.tsx | 11 ++++-- front/packages/ui/src/errors/connection.tsx | 32 +++++++++++++++-- front/packages/ui/src/errors/error.tsx | 33 ++--------------- front/packages/ui/src/errors/unauthorized.tsx | 35 ++----------------- 5 files changed, 45 insertions(+), 80 deletions(-) diff --git a/front/apps/web/src/pages/_app.tsx b/front/apps/web/src/pages/_app.tsx index b553e409..1744cd7c 100755 --- a/front/apps/web/src/pages/_app.tsx +++ b/front/apps/web/src/pages/_app.tsx @@ -55,7 +55,7 @@ import arrayShuffle from "array-shuffle"; import { Tooltip } from "react-tooltip"; import { getCurrentAccount, readCookie, updateAccount } from "@kyoo/models/src/account-internal"; import { PortalProvider } from "@gorhom/portal"; -import { ConnectionError, ErrorContext } from "@kyoo/ui"; +import { ConnectionError } from "@kyoo/ui"; const font = Poppins({ weight: ["300", "400", "900"], subsets: ["latin"], display: "swap" }); @@ -136,17 +136,7 @@ const WithLayout = ({ Component, ...props }: { Component: ComponentType }) => { const layoutInfo = (Component as QueryPage).getLayout ?? (({ page }) => page); const { Layout, props: layoutProps } = typeof layoutInfo === "function" ? { Layout: layoutInfo, props: {} } : layoutInfo; - return ( - - - - } - randomItems={[]} - {...layoutProps} - /> - ); + return } randomItems={[]} {...layoutProps} />; }; const App = ({ Component, pageProps }: AppProps) => { diff --git a/front/packages/models/src/accounts.tsx b/front/packages/models/src/accounts.tsx index b9f93d45..64c58ed8 100644 --- a/front/packages/models/src/accounts.tsx +++ b/front/packages/models/src/accounts.tsx @@ -18,7 +18,7 @@ * along with Kyoo. If not, see . */ -import { ReactNode, createContext, useContext, useEffect, useMemo, useRef } from "react"; +import { ReactNode, createContext, useContext, useEffect, useMemo, useRef, useState } from "react"; import { ServerInfoP, User, UserP } from "./resources"; import { z } from "zod"; import { zdate } from "./utils"; @@ -69,7 +69,8 @@ export const ConnectionErrorContext = createContext<{ error: KyooErrors | null; loading: boolean; retry?: () => void; -}>({ error: null, loading: true }); + setError: (error: KyooErrors) => void; +}>({ error: null, loading: true, setError: () => {} }); /* eslint-disable react-hooks/rules-of-hooks */ export const AccountProvider = ({ @@ -96,6 +97,7 @@ export const AccountProvider = ({ retry: () => { queryClient.resetQueries({ queryKey: ["auth", "me"] }); }, + setError: () => {}, }} > {children} @@ -156,15 +158,18 @@ export const AccountProvider = ({ } }, [selected, queryClient]); + const [permissionError, setPermissionError] = useState(null); + return ( { queryClient.invalidateQueries({ queryKey: ["auth", "me"] }); }, + setError: setPermissionError, }} > {children} diff --git a/front/packages/ui/src/errors/connection.tsx b/front/packages/ui/src/errors/connection.tsx index 2218dd78..b1b17c9c 100644 --- a/front/packages/ui/src/errors/connection.tsx +++ b/front/packages/ui/src/errors/connection.tsx @@ -18,21 +18,49 @@ * along with Kyoo. If not, see . */ -import { ConnectionErrorContext } from "@kyoo/models"; -import { Button, H1, P, ts } from "@kyoo/primitives"; +import { ConnectionErrorContext, useAccount } from "@kyoo/models"; +import { Button, H1, Icon, Link, P, ts } from "@kyoo/primitives"; import { useRouter } from "solito/router"; import { useContext } from "react"; import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { useYoshiki } from "yoshiki/native"; import { DefaultLayout } from "../layout"; +import { ErrorView } from "./error"; +import Register from "@material-symbols/svg-400/rounded/app_registration.svg"; export const ConnectionError = () => { const { css } = useYoshiki(); const { t } = useTranslation(); const router = useRouter(); const { error, retry } = useContext(ConnectionErrorContext); + const account = useAccount(); + if (error && (error.status === 401 || error.status == 403)) { + if (!account) { + return ( + +

{t("errors.needAccount")}

+