feat: song likes and initial implementation of Spotify playlist support, refs #9
This commit is contained in:
		@@ -12,9 +12,10 @@ public class DataContext : DbContext
 | 
			
		||||
    public DbSet<Song>? Songs { get; set; }
 | 
			
		||||
    public DbSet<SongSuggestion>? SongSuggestions { get; set; }
 | 
			
		||||
    public DbSet<SuggestionHelper>? SuggestionHelpers { get; set; }
 | 
			
		||||
    public DbSet<SmartPlaylistDefinition>? SmartPlaylistDefinitions { get; set; }
 | 
			
		||||
 | 
			
		||||
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 | 
			
		||||
        => optionsBuilder.UseNpgsql($"Host={AppConfiguration.Instance.DatabaseUri}:{AppConfiguration.Instance.DatabasePort};"
 | 
			
		||||
                                    + $"Username={AppConfiguration.Instance.DatabaseUser};Password={AppConfiguration.Instance.DatabasePW};"
 | 
			
		||||
                                    + $"Database={AppConfiguration.Instance.DatabaseName}");
 | 
			
		||||
                                    + $"Database={AppConfiguration.Instance.DatabaseName}");    
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										268
									
								
								song_of_the_day/Data/Migrations/20250719185147_adding song likes and playlists.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								song_of_the_day/Data/Migrations/20250719185147_adding song likes and playlists.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,268 @@
 | 
			
		||||
// <auto-generated />
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
			
		||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    [DbContext(typeof(DataContext))]
 | 
			
		||||
    [Migration("20250719185147_adding song likes and playlists")]
 | 
			
		||||
    partial class addingsonglikesandplaylists
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
			
		||||
        {
 | 
			
		||||
#pragma warning disable 612, 618
 | 
			
		||||
            modelBuilder
 | 
			
		||||
                .HasAnnotation("ProductVersion", "9.0.3")
 | 
			
		||||
                .HasAnnotation("Relational:MaxIdentifierLength", 63);
 | 
			
		||||
 | 
			
		||||
            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("CreatedByUserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesLikedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesUnCategorizedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyPlaylistId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SmartPlaylistDefinitions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("SongId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Artist")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("Provider")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId1")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Url")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Songs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("Date")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("HasUsedSuggestion")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SongId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SuggestionHelperId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("UserHasSubmitted")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SuggestionHelperId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SongSuggestions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SuggestionHelpers");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("UserId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("AssociationInProgress")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IsIntroduced")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("LdapUserName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("NickName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SignalMemberId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("WasChosenForSuggestionThisRound")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Users");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("User", "CreatedBy")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("CreatedBy");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyExcludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyIncludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", null)
 | 
			
		||||
                        .WithMany("LikedSongs")
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("Song", "Song")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SuggestionHelper", "SuggestionHelper")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SuggestionHelperId")
 | 
			
		||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
			
		||||
                        .IsRequired();
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", "User")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("Song");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("SuggestionHelper");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("User");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("Categories")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("Categories");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyExcludedSongs");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyIncludedSongs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("LikedSongs");
 | 
			
		||||
                });
 | 
			
		||||
#pragma warning restore 612, 618
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,170 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc />
 | 
			
		||||
    public partial class addingsonglikesandplaylists : Migration
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.AddColumn<int>(
 | 
			
		||||
                name: "SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "SuggestionHelpers",
 | 
			
		||||
                type: "integer",
 | 
			
		||||
                nullable: true);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<int>(
 | 
			
		||||
                name: "SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                type: "integer",
 | 
			
		||||
                nullable: true);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<int>(
 | 
			
		||||
                name: "SmartPlaylistDefinitionId1",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                type: "integer",
 | 
			
		||||
                nullable: true);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<int>(
 | 
			
		||||
                name: "UserId",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                type: "integer",
 | 
			
		||||
                nullable: true);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.CreateTable(
 | 
			
		||||
                name: "SmartPlaylistDefinitions",
 | 
			
		||||
                columns: table => new
 | 
			
		||||
                {
 | 
			
		||||
                    Id = table.Column<int>(type: "integer", nullable: false)
 | 
			
		||||
                        .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
 | 
			
		||||
                    Title = table.Column<string>(type: "text", nullable: true),
 | 
			
		||||
                    Description = table.Column<string>(type: "text", nullable: true),
 | 
			
		||||
                    CreatedByUserId = table.Column<int>(type: "integer", nullable: true),
 | 
			
		||||
                    IncludesUnCategorizedSongs = table.Column<bool>(type: "boolean", nullable: false),
 | 
			
		||||
                    IncludesLikedSongs = table.Column<bool>(type: "boolean", nullable: false),
 | 
			
		||||
                    SpotifyPlaylistId = table.Column<string>(type: "text", nullable: true)
 | 
			
		||||
                },
 | 
			
		||||
                constraints: table =>
 | 
			
		||||
                {
 | 
			
		||||
                    table.PrimaryKey("PK_SmartPlaylistDefinitions", x => x.Id);
 | 
			
		||||
                    table.ForeignKey(
 | 
			
		||||
                        name: "FK_SmartPlaylistDefinitions_Users_CreatedByUserId",
 | 
			
		||||
                        column: x => x.CreatedByUserId,
 | 
			
		||||
                        principalTable: "Users",
 | 
			
		||||
                        principalColumn: "UserId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.CreateIndex(
 | 
			
		||||
                name: "IX_SuggestionHelpers_SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "SuggestionHelpers",
 | 
			
		||||
                column: "SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.CreateIndex(
 | 
			
		||||
                name: "IX_Songs_SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                column: "SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.CreateIndex(
 | 
			
		||||
                name: "IX_Songs_SmartPlaylistDefinitionId1",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                column: "SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.CreateIndex(
 | 
			
		||||
                name: "IX_Songs_UserId",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                column: "UserId");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.CreateIndex(
 | 
			
		||||
                name: "IX_SmartPlaylistDefinitions_CreatedByUserId",
 | 
			
		||||
                table: "SmartPlaylistDefinitions",
 | 
			
		||||
                column: "CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddForeignKey(
 | 
			
		||||
                name: "FK_Songs_SmartPlaylistDefinitions_SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                column: "SmartPlaylistDefinitionId",
 | 
			
		||||
                principalTable: "SmartPlaylistDefinitions",
 | 
			
		||||
                principalColumn: "Id");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddForeignKey(
 | 
			
		||||
                name: "FK_Songs_SmartPlaylistDefinitions_SmartPlaylistDefinitionId1",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                column: "SmartPlaylistDefinitionId1",
 | 
			
		||||
                principalTable: "SmartPlaylistDefinitions",
 | 
			
		||||
                principalColumn: "Id");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddForeignKey(
 | 
			
		||||
                name: "FK_Songs_Users_UserId",
 | 
			
		||||
                table: "Songs",
 | 
			
		||||
                column: "UserId",
 | 
			
		||||
                principalTable: "Users",
 | 
			
		||||
                principalColumn: "UserId");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddForeignKey(
 | 
			
		||||
                name: "FK_SuggestionHelpers_SmartPlaylistDefinitions_SmartPlaylistDef~",
 | 
			
		||||
                table: "SuggestionHelpers",
 | 
			
		||||
                column: "SmartPlaylistDefinitionId",
 | 
			
		||||
                principalTable: "SmartPlaylistDefinitions",
 | 
			
		||||
                principalColumn: "Id");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.DropForeignKey(
 | 
			
		||||
                name: "FK_Songs_SmartPlaylistDefinitions_SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropForeignKey(
 | 
			
		||||
                name: "FK_Songs_SmartPlaylistDefinitions_SmartPlaylistDefinitionId1",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropForeignKey(
 | 
			
		||||
                name: "FK_Songs_Users_UserId",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropForeignKey(
 | 
			
		||||
                name: "FK_SuggestionHelpers_SmartPlaylistDefinitions_SmartPlaylistDef~",
 | 
			
		||||
                table: "SuggestionHelpers");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropTable(
 | 
			
		||||
                name: "SmartPlaylistDefinitions");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropIndex(
 | 
			
		||||
                name: "IX_SuggestionHelpers_SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "SuggestionHelpers");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropIndex(
 | 
			
		||||
                name: "IX_Songs_SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropIndex(
 | 
			
		||||
                name: "IX_Songs_SmartPlaylistDefinitionId1",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropIndex(
 | 
			
		||||
                name: "IX_Songs_UserId",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "SuggestionHelpers");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "SmartPlaylistDefinitionId",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "SmartPlaylistDefinitionId1",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "UserId",
 | 
			
		||||
                table: "Songs");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,282 @@
 | 
			
		||||
// <auto-generated />
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
			
		||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    [DbContext(typeof(DataContext))]
 | 
			
		||||
    [Migration("20250719222759_fix issues with Spotify session data for users")]
 | 
			
		||||
    partial class fixissueswithSpotifysessiondataforusers
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
			
		||||
        {
 | 
			
		||||
#pragma warning disable 612, 618
 | 
			
		||||
            modelBuilder
 | 
			
		||||
                .HasAnnotation("ProductVersion", "9.0.3")
 | 
			
		||||
                .HasAnnotation("Relational:MaxIdentifierLength", 63);
 | 
			
		||||
 | 
			
		||||
            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("CreatedByUserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesLikedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesUnCategorizedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyPlaylistId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SmartPlaylistDefinitions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("SongId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Artist")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("Provider")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId1")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Url")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Songs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("Date")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("HasUsedSuggestion")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SongId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SuggestionHelperId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("UserHasSubmitted")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SuggestionHelperId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SongSuggestions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SuggestionHelpers");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("UserId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("AssociationInProgress")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IsIntroduced")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("LdapUserName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("NickName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SignalMemberId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotiyAuthAccessToken")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("SpotiyAuthCreatedAt")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SpotiyAuthExpiresAfterSeconds")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotiyAuthRefreshToken")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("WasChosenForSuggestionThisRound")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Users");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("User", "CreatedBy")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("CreatedBy");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyExcludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyIncludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", null)
 | 
			
		||||
                        .WithMany("LikedSongs")
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("Song", "Song")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SuggestionHelper", "SuggestionHelper")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SuggestionHelperId")
 | 
			
		||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
			
		||||
                        .IsRequired();
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", "User")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("Song");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("SuggestionHelper");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("User");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("Categories")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("Categories");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyExcludedSongs");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyIncludedSongs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("LikedSongs");
 | 
			
		||||
                });
 | 
			
		||||
#pragma warning restore 612, 618
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc />
 | 
			
		||||
    public partial class fixissueswithSpotifysessiondataforusers : Migration
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.AddColumn<string>(
 | 
			
		||||
                name: "SpotiyAuthAccessToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "text",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: "");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<DateTime>(
 | 
			
		||||
                name: "SpotiyAuthCreatedAt",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "timestamp with time zone",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<int>(
 | 
			
		||||
                name: "SpotiyAuthExpiresAfterSeconds",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "integer",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: 0);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AddColumn<string>(
 | 
			
		||||
                name: "SpotiyAuthRefreshToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "text",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: "");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "SpotiyAuthAccessToken",
 | 
			
		||||
                table: "Users");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "SpotiyAuthCreatedAt",
 | 
			
		||||
                table: "Users");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "SpotiyAuthExpiresAfterSeconds",
 | 
			
		||||
                table: "Users");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.DropColumn(
 | 
			
		||||
                name: "SpotiyAuthRefreshToken",
 | 
			
		||||
                table: "Users");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										282
									
								
								song_of_the_day/Data/Migrations/20250719230009_some more fixes.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								song_of_the_day/Data/Migrations/20250719230009_some more fixes.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,282 @@
 | 
			
		||||
// <auto-generated />
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
			
		||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    [DbContext(typeof(DataContext))]
 | 
			
		||||
    [Migration("20250719230009_some more fixes")]
 | 
			
		||||
    partial class somemorefixes
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
			
		||||
        {
 | 
			
		||||
#pragma warning disable 612, 618
 | 
			
		||||
            modelBuilder
 | 
			
		||||
                .HasAnnotation("ProductVersion", "9.0.3")
 | 
			
		||||
                .HasAnnotation("Relational:MaxIdentifierLength", 63);
 | 
			
		||||
 | 
			
		||||
            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("CreatedByUserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesLikedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesUnCategorizedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyPlaylistId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SmartPlaylistDefinitions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("SongId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Artist")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("Provider")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId1")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Url")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Songs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("Date")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("HasUsedSuggestion")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SongId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SuggestionHelperId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("UserHasSubmitted")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SuggestionHelperId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SongSuggestions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SuggestionHelpers");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("UserId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("AssociationInProgress")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IsIntroduced")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("LdapUserName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("NickName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SignalMemberId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthAccessToken")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("SpotifyAuthCreatedAt")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SpotifyAuthExpiresAfterSeconds")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthRefreshToken")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("WasChosenForSuggestionThisRound")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Users");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("User", "CreatedBy")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("CreatedBy");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyExcludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyIncludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", null)
 | 
			
		||||
                        .WithMany("LikedSongs")
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("Song", "Song")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SuggestionHelper", "SuggestionHelper")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SuggestionHelperId")
 | 
			
		||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
			
		||||
                        .IsRequired();
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", "User")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("Song");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("SuggestionHelper");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("User");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("Categories")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("Categories");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyExcludedSongs");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyIncludedSongs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("LikedSongs");
 | 
			
		||||
                });
 | 
			
		||||
#pragma warning restore 612, 618
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,58 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc />
 | 
			
		||||
    public partial class somemorefixes : Migration
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotiyAuthRefreshToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotifyAuthRefreshToken");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotiyAuthExpiresAfterSeconds",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotifyAuthExpiresAfterSeconds");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotiyAuthCreatedAt",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotifyAuthCreatedAt");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotiyAuthAccessToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotifyAuthAccessToken");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotifyAuthRefreshToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotiyAuthRefreshToken");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotifyAuthExpiresAfterSeconds",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotiyAuthExpiresAfterSeconds");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotifyAuthCreatedAt",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotiyAuthCreatedAt");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.RenameColumn(
 | 
			
		||||
                name: "SpotifyAuthAccessToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                newName: "SpotiyAuthAccessToken");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										282
									
								
								song_of_the_day/Data/Migrations/20250719233912_explicitly specify keys.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								song_of_the_day/Data/Migrations/20250719233912_explicitly specify keys.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,282 @@
 | 
			
		||||
// <auto-generated />
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
			
		||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    [DbContext(typeof(DataContext))]
 | 
			
		||||
    [Migration("20250719233912_explicitly specify keys")]
 | 
			
		||||
    partial class explicitlyspecifykeys
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
			
		||||
        {
 | 
			
		||||
#pragma warning disable 612, 618
 | 
			
		||||
            modelBuilder
 | 
			
		||||
                .HasAnnotation("ProductVersion", "9.0.3")
 | 
			
		||||
                .HasAnnotation("Relational:MaxIdentifierLength", 63);
 | 
			
		||||
 | 
			
		||||
            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("CreatedByUserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesLikedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesUnCategorizedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyPlaylistId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SmartPlaylistDefinitions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("SongId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Artist")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("Provider")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId1")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Url")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Songs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("Date")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("HasUsedSuggestion")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SongId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SuggestionHelperId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("UserHasSubmitted")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SuggestionHelperId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SongSuggestions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SuggestionHelpers");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("UserId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("AssociationInProgress")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IsIntroduced")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("LdapUserName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("NickName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SignalMemberId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthAccessToken")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("SpotifyAuthCreatedAt")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SpotifyAuthExpiresAfterSeconds")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthRefreshToken")
 | 
			
		||||
                        .IsRequired()
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("WasChosenForSuggestionThisRound")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Users");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("User", "CreatedBy")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("CreatedBy");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyExcludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyIncludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", null)
 | 
			
		||||
                        .WithMany("LikedSongs")
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("Song", "Song")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SuggestionHelper", "SuggestionHelper")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SuggestionHelperId")
 | 
			
		||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
			
		||||
                        .IsRequired();
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", "User")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("Song");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("SuggestionHelper");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("User");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("Categories")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("Categories");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyExcludedSongs");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyIncludedSongs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("LikedSongs");
 | 
			
		||||
                });
 | 
			
		||||
#pragma warning restore 612, 618
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc />
 | 
			
		||||
    public partial class explicitlyspecifykeys : Migration
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										280
									
								
								song_of_the_day/Data/Migrations/20250720003809_fix auth token non-null contraint.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								song_of_the_day/Data/Migrations/20250720003809_fix auth token non-null contraint.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,280 @@
 | 
			
		||||
// <auto-generated />
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
			
		||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    [DbContext(typeof(DataContext))]
 | 
			
		||||
    [Migration("20250720003809_fix auth token non-null contraint")]
 | 
			
		||||
    partial class fixauthtokennonnullcontraint
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
			
		||||
        {
 | 
			
		||||
#pragma warning disable 612, 618
 | 
			
		||||
            modelBuilder
 | 
			
		||||
                .HasAnnotation("ProductVersion", "9.0.3")
 | 
			
		||||
                .HasAnnotation("Relational:MaxIdentifierLength", 63);
 | 
			
		||||
 | 
			
		||||
            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("CreatedByUserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesLikedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesUnCategorizedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyPlaylistId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SmartPlaylistDefinitions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("SongId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Artist")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("Provider")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId1")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Url")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Songs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime>("Date")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("HasUsedSuggestion")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SongId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int>("SuggestionHelperId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("UserHasSubmitted")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SuggestionHelperId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SongSuggestions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SuggestionHelpers");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("UserId")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("AssociationInProgress")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IsIntroduced")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("LdapUserName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Name")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("NickName")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SignalMemberId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthAccessToken")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime?>("SpotifyAuthCreatedAt")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SpotifyAuthExpiresAfterSeconds")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthRefreshToken")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("WasChosenForSuggestionThisRound")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Users");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("User", "CreatedBy")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("CreatedBy");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyExcludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyIncludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", null)
 | 
			
		||||
                        .WithMany("LikedSongs")
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("Song", "Song")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SuggestionHelper", "SuggestionHelper")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("SuggestionHelperId")
 | 
			
		||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
			
		||||
                        .IsRequired();
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", "User")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("Song");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("SuggestionHelper");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("User");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("Categories")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("Categories");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyExcludedSongs");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyIncludedSongs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("LikedSongs");
 | 
			
		||||
                });
 | 
			
		||||
#pragma warning restore 612, 618
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,91 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Migrations;
 | 
			
		||||
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace song_of_the_day.DataMigrations
 | 
			
		||||
{
 | 
			
		||||
    /// <inheritdoc />
 | 
			
		||||
    public partial class fixauthtokennonnullcontraint : Migration
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Up(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.AlterColumn<string>(
 | 
			
		||||
                name: "SpotifyAuthRefreshToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "text",
 | 
			
		||||
                nullable: true,
 | 
			
		||||
                oldClrType: typeof(string),
 | 
			
		||||
                oldType: "text");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<int>(
 | 
			
		||||
                name: "SpotifyAuthExpiresAfterSeconds",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "integer",
 | 
			
		||||
                nullable: true,
 | 
			
		||||
                oldClrType: typeof(int),
 | 
			
		||||
                oldType: "integer");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<DateTime>(
 | 
			
		||||
                name: "SpotifyAuthCreatedAt",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "timestamp with time zone",
 | 
			
		||||
                nullable: true,
 | 
			
		||||
                oldClrType: typeof(DateTime),
 | 
			
		||||
                oldType: "timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<string>(
 | 
			
		||||
                name: "SpotifyAuthAccessToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "text",
 | 
			
		||||
                nullable: true,
 | 
			
		||||
                oldClrType: typeof(string),
 | 
			
		||||
                oldType: "text");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        protected override void Down(MigrationBuilder migrationBuilder)
 | 
			
		||||
        {
 | 
			
		||||
            migrationBuilder.AlterColumn<string>(
 | 
			
		||||
                name: "SpotifyAuthRefreshToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "text",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: "",
 | 
			
		||||
                oldClrType: typeof(string),
 | 
			
		||||
                oldType: "text",
 | 
			
		||||
                oldNullable: true);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<int>(
 | 
			
		||||
                name: "SpotifyAuthExpiresAfterSeconds",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "integer",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: 0,
 | 
			
		||||
                oldClrType: typeof(int),
 | 
			
		||||
                oldType: "integer",
 | 
			
		||||
                oldNullable: true);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<DateTime>(
 | 
			
		||||
                name: "SpotifyAuthCreatedAt",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "timestamp with time zone",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
 | 
			
		||||
                oldClrType: typeof(DateTime),
 | 
			
		||||
                oldType: "timestamp with time zone",
 | 
			
		||||
                oldNullable: true);
 | 
			
		||||
 | 
			
		||||
            migrationBuilder.AlterColumn<string>(
 | 
			
		||||
                name: "SpotifyAuthAccessToken",
 | 
			
		||||
                table: "Users",
 | 
			
		||||
                type: "text",
 | 
			
		||||
                nullable: false,
 | 
			
		||||
                defaultValue: "",
 | 
			
		||||
                oldClrType: typeof(string),
 | 
			
		||||
                oldType: "text",
 | 
			
		||||
                oldNullable: true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -21,6 +21,39 @@ namespace song_of_the_day.DataMigrations
 | 
			
		||||
 | 
			
		||||
            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("Id")
 | 
			
		||||
                        .ValueGeneratedOnAdd()
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("CreatedByUserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesLikedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("IncludesUnCategorizedSongs")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyPlaylistId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SmartPlaylistDefinitions");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Property<int>("SongId")
 | 
			
		||||
@@ -38,14 +71,29 @@ namespace song_of_the_day.DataMigrations
 | 
			
		||||
                    b.Property<int?>("Provider")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId1")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Url")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("UserId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("SongId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("UserId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("Songs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
@@ -97,11 +145,16 @@ namespace song_of_the_day.DataMigrations
 | 
			
		||||
                    b.Property<string>("Description")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SmartPlaylistDefinitionId")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("Title")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.HasKey("Id");
 | 
			
		||||
 | 
			
		||||
                    b.HasIndex("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.ToTable("SuggestionHelpers");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
@@ -131,6 +184,18 @@ namespace song_of_the_day.DataMigrations
 | 
			
		||||
                    b.Property<string>("SignalMemberId")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthAccessToken")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<DateTime?>("SpotifyAuthCreatedAt")
 | 
			
		||||
                        .HasColumnType("timestamp with time zone");
 | 
			
		||||
 | 
			
		||||
                    b.Property<int?>("SpotifyAuthExpiresAfterSeconds")
 | 
			
		||||
                        .HasColumnType("integer");
 | 
			
		||||
 | 
			
		||||
                    b.Property<string>("SpotifyAuthRefreshToken")
 | 
			
		||||
                        .HasColumnType("text");
 | 
			
		||||
 | 
			
		||||
                    b.Property<bool>("WasChosenForSuggestionThisRound")
 | 
			
		||||
                        .HasColumnType("boolean");
 | 
			
		||||
 | 
			
		||||
@@ -139,6 +204,30 @@ namespace song_of_the_day.DataMigrations
 | 
			
		||||
                    b.ToTable("Users");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("User", "CreatedBy")
 | 
			
		||||
                        .WithMany()
 | 
			
		||||
                        .HasForeignKey("CreatedByUserId");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("CreatedBy");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("Song", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyExcludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("ExplicitlyIncludedSongs")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId1");
 | 
			
		||||
 | 
			
		||||
                    b.HasOne("User", null)
 | 
			
		||||
                        .WithMany("LikedSongs")
 | 
			
		||||
                        .HasForeignKey("UserId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SongSuggestion", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("Song", "Song")
 | 
			
		||||
@@ -161,6 +250,27 @@ namespace song_of_the_day.DataMigrations
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("User");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SuggestionHelper", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.HasOne("SmartPlaylistDefinition", null)
 | 
			
		||||
                        .WithMany("Categories")
 | 
			
		||||
                        .HasForeignKey("SmartPlaylistDefinitionId");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("SmartPlaylistDefinition", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("Categories");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyExcludedSongs");
 | 
			
		||||
 | 
			
		||||
                    b.Navigation("ExplicitlyIncludedSongs");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            modelBuilder.Entity("User", b =>
 | 
			
		||||
                {
 | 
			
		||||
                    b.Navigation("LikedSongs");
 | 
			
		||||
                });
 | 
			
		||||
#pragma warning restore 612, 618
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								song_of_the_day/Data/SmartPlaylistDefinition.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								song_of_the_day/Data/SmartPlaylistDefinition.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
 | 
			
		||||
[PrimaryKey(nameof(Id))]
 | 
			
		||||
public class SmartPlaylistDefinition
 | 
			
		||||
{
 | 
			
		||||
    public int Id { get; set; }
 | 
			
		||||
    public string? Title { get; set; }
 | 
			
		||||
    public string? Description { get; set; }
 | 
			
		||||
    public User? CreatedBy { get; set; }
 | 
			
		||||
    public bool IncludesUnCategorizedSongs { get; set; }
 | 
			
		||||
    public bool IncludesLikedSongs { get; set; }
 | 
			
		||||
    public List<SuggestionHelper>? Categories { get; set; }
 | 
			
		||||
    public string? SpotifyPlaylistId { get; set; }
 | 
			
		||||
 | 
			
		||||
    public List<Song>? ExplicitlyIncludedSongs { get; set; }
 | 
			
		||||
    public List<Song>? ExplicitlyExcludedSongs { get; set; }
 | 
			
		||||
 | 
			
		||||
    public bool IsThisUsersLikedSongsPlaylist
 | 
			
		||||
    {
 | 
			
		||||
        get
 | 
			
		||||
        {
 | 
			
		||||
            return IncludesLikedSongs == true
 | 
			
		||||
                    && IncludesUnCategorizedSongs == false
 | 
			
		||||
                    && (ExplicitlyExcludedSongs == null || ExplicitlyExcludedSongs.Count == 0)
 | 
			
		||||
                    && (ExplicitlyIncludedSongs == null || ExplicitlyIncludedSongs.Count == 0)
 | 
			
		||||
                    && (Categories == null || Categories.Count == 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -10,4 +10,17 @@ public class User
 | 
			
		||||
    public bool AssociationInProgress { get; set; }
 | 
			
		||||
    public string? LdapUserName { get; set; }
 | 
			
		||||
    public bool WasChosenForSuggestionThisRound { get; set; }
 | 
			
		||||
    public string PreferredName
 | 
			
		||||
    {
 | 
			
		||||
        get
 | 
			
		||||
        {
 | 
			
		||||
            return (string.IsNullOrEmpty(NickName) ? Name : NickName).ToString();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public List<Song> LikedSongs { get; set; }
 | 
			
		||||
 | 
			
		||||
    public string? SpotifyAuthAccessToken { get; set; }
 | 
			
		||||
    public int? SpotifyAuthExpiresAfterSeconds { get; set; }
 | 
			
		||||
    public DateTime? SpotifyAuthCreatedAt { get; set; }
 | 
			
		||||
    public string? SpotifyAuthRefreshToken { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
            <th>Song</th>
 | 
			
		||||
            <th>Submitter</th>
 | 
			
		||||
            <th>Details</th>
 | 
			
		||||
            <th></th>
 | 
			
		||||
        </tr>
 | 
			
		||||
        @foreach(var songSuggestion in Model.SongSuggestions)
 | 
			
		||||
        {
 | 
			
		||||
@@ -26,6 +27,17 @@
 | 
			
		||||
                    <td><a href="@songSuggestion?.Song.Url" target="_blank">@string.Format("{0} - {1}", songSuggestion?.Song.Name, songSuggestion?.Song.Artist)</a></td>
 | 
			
		||||
                    <td>@displayName</td>
 | 
			
		||||
                    <td><a href="/SongSubmission/@songSuggestion?.Id">View</a></td>
 | 
			
		||||
                    <td class="=viewLike">
 | 
			
		||||
                        @if(songSuggestion?.Song != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            var handler = Model.HasUserLikedThisSong(@songSuggestion.Song) ? "UnlikeSong" : "LikeSong";
 | 
			
		||||
                            var likebuttonText = Model.HasUserLikedThisSong(@songSuggestion.Song) ? "Unlike" : "Like";
 | 
			
		||||
                            <form method="post">
 | 
			
		||||
                                <input name="songId" value="@songSuggestion.Song.SongId" type="hidden" /> 
 | 
			
		||||
                                <input type="submit" id="songlike" value="@likebuttonText" asp-page-handler="@handler" />
 | 
			
		||||
                            </form>
 | 
			
		||||
                        }
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,14 +17,67 @@ public class IndexModel : PageModel
 | 
			
		||||
    [BindProperty]
 | 
			
		||||
    public List<SongSuggestion> SongSuggestions { get; set; } = new List<SongSuggestion>();
 | 
			
		||||
 | 
			
		||||
    public Task OnGet()
 | 
			
		||||
    {
 | 
			
		||||
    private User _currentUser;
 | 
			
		||||
 | 
			
		||||
    public User CurrentUser
 | 
			
		||||
    {
 | 
			
		||||
        get {
 | 
			
		||||
            if(_currentUser == null)
 | 
			
		||||
            {
 | 
			
		||||
                var userName = this.User.Identity.Name;
 | 
			
		||||
                using(var dci = DataContext.Instance)
 | 
			
		||||
                {
 | 
			
		||||
                    _currentUser = dci.Users.Include(u => u.LikedSongs).Where(u => u.LdapUserName == userName).SingleOrDefault();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return _currentUser;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public bool HasUserLikedThisSong(Song song)
 | 
			
		||||
    {
 | 
			
		||||
        return CurrentUser.LikedSongs.Where(s => s.SongId == song.SongId).FirstOrDefault() != default(Song);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task OnGet()
 | 
			
		||||
    {
 | 
			
		||||
        using var dci = DataContext.Instance;
 | 
			
		||||
        SongSuggestions = dci.SongSuggestions.OrderByDescending(s => s.Date)
 | 
			
		||||
            .Take(50)
 | 
			
		||||
            .Include(s => s.Song)
 | 
			
		||||
            .Include(s => s.User)
 | 
			
		||||
            .ToList();
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
            .ToList();
 | 
			
		||||
        return Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<IActionResult> OnPostLikeSong(int? songId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
 | 
			
		||||
            if (user.LikedSongs.Find(s => s.SongId == songId) == default(Song))
 | 
			
		||||
            {
 | 
			
		||||
                user.LikedSongs.Add(dci.Songs.Find(songId));
 | 
			
		||||
                await dci.SaveChangesAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return RedirectToPage("/");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<IActionResult> OnPostUnlikeSong(int? songId)
 | 
			
		||||
    {
 | 
			
		||||
        using(var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
 | 
			
		||||
            var songToRemove = user.LikedSongs.Where(s => s.SongId == songId).SingleOrDefault();
 | 
			
		||||
            if (songToRemove != default(Song))
 | 
			
		||||
            {
 | 
			
		||||
                user.LikedSongs.Remove(songToRemove);
 | 
			
		||||
                await dci.SaveChangesAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return RedirectToPage("/");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,9 +23,15 @@
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        var userName = User.Identity.Name;
 | 
			
		||||
        var dci = DataContext.Instance;
 | 
			
		||||
        var selectedUsers = dci.Users.Where(u => u.LdapUserName == userName);
 | 
			
		||||
        var userId = selectedUsers.SingleOrDefault()?.UserId;
 | 
			
		||||
        dci.Dispose();
 | 
			
		||||
 | 
			
		||||
        <form method="post" action="Auth/Logout">
 | 
			
		||||
            <div>
 | 
			
		||||
                Welcome, @User.Identity.Name!
 | 
			
		||||
                Welcome, <a href="/User/@userId">@User.Identity.Name</a>!
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <input name="submit" type="submit" value="Logout" />
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@
 | 
			
		||||
                <th>Suggestion</th>
 | 
			
		||||
                <th>Song</th>
 | 
			
		||||
                <th></th>
 | 
			
		||||
                <th></th>
 | 
			
		||||
            </tr>
 | 
			
		||||
            @foreach(var submission in Model.UserSongSubmissions)
 | 
			
		||||
            {
 | 
			
		||||
@@ -44,6 +45,17 @@
 | 
			
		||||
                        }
 | 
			
		||||
                        <button class=@buttonClass onclick="redirectTo(@submission.Id)">@buttonText</button>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td class="=viewLike">
 | 
			
		||||
                        @if(submission.Song != null)
 | 
			
		||||
                        {
 | 
			
		||||
                            var handler = Model.HasUserLikedThisSong(submission.Song) ? "UnlikeSong" : "LikeSong";
 | 
			
		||||
                            var likebuttonText = Model.HasUserLikedThisSong(submission.Song) ? "Unlike" : "Like";
 | 
			
		||||
                            <form method="post">
 | 
			
		||||
                                <input name="songId" value="@submission.Song.SongId" type="hidden" /> 
 | 
			
		||||
                                <input type="submit" id="songlike" value="@likebuttonText" asp-page-handler="@handler" />
 | 
			
		||||
                            </form>
 | 
			
		||||
                        }
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            }
 | 
			
		||||
        </table>
 | 
			
		||||
@@ -53,8 +65,8 @@ else
 | 
			
		||||
{
 | 
			
		||||
    <div class="text-left">
 | 
			
		||||
        <div class="suggestionHelper">
 | 
			
		||||
            <div class="title">Today's suggestionHelper is: <b>@Model.SuggestionHelper.Title</b></div>
 | 
			
		||||
            <div class="description" style="font-style: italic;">@Model.SuggestionHelper.Description</div>
 | 
			
		||||
            <div class="title">Today's suggestionHelper is: <b>@Model.SuggestionHelper?.Title</b></div>
 | 
			
		||||
            <div class="description" style="font-style: italic;">@Model.SuggestionHelper?.Description</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <form method="post">
 | 
			
		||||
            <label asp-for="SubmitUrl" >Song Url:</label>
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,24 @@ public class SongSubmissionModel : PageModel
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private User _currentUser;
 | 
			
		||||
 | 
			
		||||
    public User CurrentUser
 | 
			
		||||
    {
 | 
			
		||||
        get {
 | 
			
		||||
            if(_currentUser == null)
 | 
			
		||||
            {
 | 
			
		||||
                var userName = this.User.Identity.Name;
 | 
			
		||||
                using(var dci = DataContext.Instance)
 | 
			
		||||
                {
 | 
			
		||||
                    _currentUser = dci.Users.Include(u => u.LikedSongs).Where(u => u.LdapUserName == userName).SingleOrDefault();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return _currentUser;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<SpotifyAPI.Web.FullTrack> SpotifySuggestions
 | 
			
		||||
    {
 | 
			
		||||
@@ -115,6 +133,11 @@ public class SongSubmissionModel : PageModel
 | 
			
		||||
    [BindProperty(SupportsGet = true)]
 | 
			
		||||
    public bool HasUsedSuggestion { get; set; } = true;
 | 
			
		||||
 | 
			
		||||
    public bool HasUserLikedThisSong(Song song)
 | 
			
		||||
    {
 | 
			
		||||
        return CurrentUser.LikedSongs.Where(s => s.SongId == song.SongId).FirstOrDefault() != default(Song);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task UpdateGroup(SongSuggestion suggestion)
 | 
			
		||||
    {
 | 
			
		||||
        var isItToday = suggestion.Date.Date == DateTime.Today;
 | 
			
		||||
@@ -223,6 +246,35 @@ public class SongSubmissionModel : PageModel
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<IActionResult> OnPostLikeSong(int? songId)
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
 | 
			
		||||
            if (user.LikedSongs.Find(s => s.SongId == songId) == default(Song))
 | 
			
		||||
            {
 | 
			
		||||
                user.LikedSongs.Add(dci.Songs.Find(songId));
 | 
			
		||||
                await dci.SaveChangesAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return RedirectToPage("SongSubmission");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<IActionResult> OnPostUnlikeSong(int? songId)
 | 
			
		||||
    {
 | 
			
		||||
        using(var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
 | 
			
		||||
            var songToRemove = user.LikedSongs.Where(s => s.SongId == songId).SingleOrDefault();
 | 
			
		||||
            if (songToRemove != default(Song))
 | 
			
		||||
            {
 | 
			
		||||
                user.LikedSongs.Remove(songToRemove);
 | 
			
		||||
                await dci.SaveChangesAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return RedirectToPage("SongSubmission");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IActionResult OnGetUpdate()
 | 
			
		||||
    {
 | 
			
		||||
        var songUrl = Request.Query["SubmitUrl"];
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
@page "{userIndex}"
 | 
			
		||||
 | 
			
		||||
@model UserModel
 | 
			
		||||
@{
 | 
			
		||||
    ViewData["Title"] = "User #" + @Model.userId;
 | 
			
		||||
@@ -12,6 +13,20 @@
 | 
			
		||||
        <label asp-for="UserName">Contact Name</label>
 | 
			
		||||
        <input asp-for="UserName" disabled /> 
 | 
			
		||||
        <br />
 | 
			
		||||
        <input type="submit" title="Submit" />
 | 
			
		||||
        <input type="submit" value="Submit" />
 | 
			
		||||
    </form>
 | 
			
		||||
    @if(@Model.IsSpotifyAuthenticated)
 | 
			
		||||
    {
 | 
			
		||||
        <form method="post" >
 | 
			
		||||
            <input name="userIndex" value="@Model.userId" type="hidden" /> 
 | 
			
		||||
            <input type="submit" value="Deauthorize Spotify"  asp-page-handler="SpotifyLogout" />
 | 
			
		||||
        </form>
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        <form method="post" >
 | 
			
		||||
            <input name="userIndex" value="@Model.userId" type="hidden" /> 
 | 
			
		||||
            <input type="submit" value="Connect to Spotify"  asp-page-handler="SpotifyLogin" />
 | 
			
		||||
        </form>
 | 
			
		||||
    }
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
using Microsoft.AspNetCore.Components;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
			
		||||
using SpotifyAPI.Web;
 | 
			
		||||
using System.Web;
 | 
			
		||||
 | 
			
		||||
namespace sotd.Pages;
 | 
			
		||||
 | 
			
		||||
@@ -18,10 +21,12 @@ public class UserModel : PageModel
 | 
			
		||||
 | 
			
		||||
    public string UserName { get; set; }
 | 
			
		||||
 | 
			
		||||
    public bool IsSpotifyAuthenticated { get; set; }
 | 
			
		||||
 | 
			
		||||
    [BindProperty]
 | 
			
		||||
    public string UserNickName { get; set; }
 | 
			
		||||
 | 
			
		||||
    public void OnGet(int userIndex)
 | 
			
		||||
    public async Task OnGet(int userIndex, [FromServices] SpotifyApiClient spotifyClient)
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
@@ -29,10 +34,11 @@ public class UserModel : PageModel
 | 
			
		||||
            this.UserName = user == null ? string.Empty : (user.Name ?? string.Empty);
 | 
			
		||||
            this.UserNickName = user == null ? string.Empty : (user.NickName ?? string.Empty);
 | 
			
		||||
            this.userId = userIndex;
 | 
			
		||||
            this.IsSpotifyAuthenticated = await spotifyClient.IsUserAuthenticatedAsync(user);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void OnPost(int userIndex)
 | 
			
		||||
    public async Task OnPost(int userIndex, [FromServices] SpotifyApiClient spotifyClient)
 | 
			
		||||
    {
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
@@ -42,6 +48,39 @@ public class UserModel : PageModel
 | 
			
		||||
                user.NickName = this.UserNickName;
 | 
			
		||||
                dci.SaveChanges();
 | 
			
		||||
                this.UserName = user.Name ?? string.Empty;
 | 
			
		||||
                this.IsSpotifyAuthenticated = await spotifyClient.IsUserAuthenticatedAsync(user);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public IActionResult OnPostSpotifyLogin(int userIndex, string currentUri, [FromServices] SpotifyApiClient spotifyClient)
 | 
			
		||||
    {
 | 
			
		||||
        _logger.LogTrace($"Attempting Spotify login for user with id {userIndex}.");
 | 
			
		||||
 | 
			
		||||
        var loginRequest = new LoginRequest(
 | 
			
		||||
                    new Uri(spotifyClient.GetLoginRedirectUri()),
 | 
			
		||||
                    AppConfiguration.Instance.SpotifyClientId,
 | 
			
		||||
                    LoginRequest.ResponseType.Code)
 | 
			
		||||
            {
 | 
			
		||||
                Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative, Scopes.PlaylistModifyPrivate, Scopes.PlaylistModifyPublic, Scopes.UgcImageUpload }
 | 
			
		||||
            };
 | 
			
		||||
        var redirectUri = loginRequest.ToUri().ToString() + $"&finalRedirect={HttpUtility.UrlEncode($"{Request.Scheme}://{Request.Host}:{Request.Host.Port ?? 80}{Request.Path}")}";
 | 
			
		||||
 | 
			
		||||
        return this.Redirect(redirectUri);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task OnPostSpotifyLogout(int userIndex, [FromServices] SpotifyApiClient spotifyClient)
 | 
			
		||||
    {
 | 
			
		||||
        _logger.LogTrace($"Attempting to deauthorize Spotify for user with id {userIndex}.");
 | 
			
		||||
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var user = dci.Users?.Find(userIndex);
 | 
			
		||||
            if (user != null)
 | 
			
		||||
            {
 | 
			
		||||
                await spotifyClient.DeAuthorizeUserAsync(user);
 | 
			
		||||
                user.NickName = this.UserNickName;
 | 
			
		||||
                this.UserName = user.Name ?? string.Empty;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,9 @@
 | 
			
		||||
using Scalar.AspNetCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.AspNetCore.Authentication.Cookies;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using SpotifyAPI.Web;
 | 
			
		||||
using Microsoft.AspNetCore.Components.Authorization;
 | 
			
		||||
 | 
			
		||||
var builder = WebApplication.CreateBuilder(args);
 | 
			
		||||
 | 
			
		||||
@@ -21,6 +24,7 @@ builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
 | 
			
		||||
builder.Services.AddSingleton<SignalIntegration>();
 | 
			
		||||
builder.Services.AddSingleton<LdapIntegration>();
 | 
			
		||||
builder.Services.AddSingleton<SpotifyApiClient>();
 | 
			
		||||
builder.Services.AddSingleton<PlayListSynchronizer>();
 | 
			
		||||
builder.Services.AddSingleton<SongResolver>();
 | 
			
		||||
 | 
			
		||||
var app = builder.Build();
 | 
			
		||||
@@ -66,6 +70,43 @@ userCheckTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
    await dci.DisposeAsync();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
logger.LogTrace("Setting up liked songs playlist creation timer");
 | 
			
		||||
var likePlaylistCheckTimer = new CronTimer("*/30 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
			
		||||
likePlaylistCheckTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
{
 | 
			
		||||
    var spotifyApiClient = app.Services.GetService<SpotifyApiClient>();
 | 
			
		||||
    var playlistSynchronizer = app.Services.GetService<PlayListSynchronizer>();
 | 
			
		||||
    var dci = DataContext.Instance;
 | 
			
		||||
    var needsSaving = false;
 | 
			
		||||
    var allUsers = dci.Users.ToList();
 | 
			
		||||
    await dci.DisposeAsync();
 | 
			
		||||
    dci = DataContext.Instance;
 | 
			
		||||
    foreach (var user in allUsers)
 | 
			
		||||
    {
 | 
			
		||||
        if (!await spotifyApiClient.IsUserAuthenticatedAsync(user))
 | 
			
		||||
        {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        var foundPlaylist = dci.SmartPlaylistDefinitions?.Where(p => p.CreatedBy == user).ToList().Where(p => p.IsThisUsersLikedSongsPlaylist).SingleOrDefault();
 | 
			
		||||
        if (foundPlaylist == null)
 | 
			
		||||
        {
 | 
			
		||||
            var title = $"{user.PreferredName}'s liked Songs";
 | 
			
		||||
            var description = $"A collection of the songs liked by {user.PreferredName} on their 'Song of the day' server instance.";
 | 
			
		||||
            // playlist does not exist yet, creating it
 | 
			
		||||
            var newPlaylist = await (await spotifyApiClient.WithUserAuthorizationAsync(user)).CreateSpotifyPlaylist(title, description, false, true, user);
 | 
			
		||||
            await playlistSynchronizer.SynchronizePlaylistAsync(newPlaylist);
 | 
			
		||||
            needsSaving = true;
 | 
			
		||||
        }
 | 
			
		||||
        await playlistSynchronizer.SynchronizeUserPlaylistsAsync(user);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (needsSaving)
 | 
			
		||||
    {
 | 
			
		||||
        await dci.SaveChangesAsync();
 | 
			
		||||
    }
 | 
			
		||||
    await dci.DisposeAsync();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
logger.LogTrace("Setting up user intro timer");
 | 
			
		||||
var userIntroTimer = new CronTimer("*/1 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
			
		||||
userIntroTimer.OnOccurence += async (s, ea) =>
 | 
			
		||||
@@ -217,6 +258,7 @@ if (!app.Environment.IsDevelopment())
 | 
			
		||||
    userIntroTimer.Start();
 | 
			
		||||
    pickOfTheDayTimer.Start();
 | 
			
		||||
    ldapAssociationTimer.Start();
 | 
			
		||||
    likePlaylistCheckTimer.Start();
 | 
			
		||||
}
 | 
			
		||||
else
 | 
			
		||||
{
 | 
			
		||||
@@ -249,6 +291,26 @@ app.MapControllerRoute(
 | 
			
		||||
    name: "logout",
 | 
			
		||||
    pattern: "{controller=Auth}/{action=Logout}"
 | 
			
		||||
);
 | 
			
		||||
app.MapGet("SpotifyLogin", async (HttpRequest request, HttpResponse response) => {
 | 
			
		||||
    var spotifyClient = app.Services.GetService<SpotifyApiClient>();
 | 
			
		||||
    var code = request.Query["code"];
 | 
			
		||||
    var oAuthResponse = await new OAuthClient().RequestToken(
 | 
			
		||||
        new AuthorizationCodeTokenRequest(AppConfiguration.Instance.SpotifyClientId, AppConfiguration.Instance.SpotifyClientSecret, code, new Uri(spotifyClient.GetLoginRedirectUri()))
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    var dci = DataContext.Instance;
 | 
			
		||||
    var userName = request.HttpContext.User.Identity.Name;
 | 
			
		||||
    var loggedInUser = dci.Users.Where(u => u.LdapUserName == userName).SingleOrDefault();
 | 
			
		||||
    var userId = loggedInUser.UserId;
 | 
			
		||||
    loggedInUser.SpotifyAuthAccessToken = oAuthResponse.AccessToken;
 | 
			
		||||
    loggedInUser.SpotifyAuthExpiresAfterSeconds = oAuthResponse.ExpiresIn;
 | 
			
		||||
    loggedInUser.SpotifyAuthCreatedAt = oAuthResponse.CreatedAt;
 | 
			
		||||
    loggedInUser.SpotifyAuthRefreshToken = oAuthResponse.RefreshToken;
 | 
			
		||||
    await dci.SaveChangesAsync();
 | 
			
		||||
    dci.Dispose();
 | 
			
		||||
 | 
			
		||||
    response.Redirect($"/User/{userId}");
 | 
			
		||||
});
 | 
			
		||||
app.MapGet("/debug/routes", (IEnumerable<EndpointDataSource> endpointSources) =>
 | 
			
		||||
        string.Join("\n", endpointSources.SelectMany(source => source.Endpoints)));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										65
									
								
								song_of_the_day/SpotifyIntegration/PlayListSynchronizer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								song_of_the_day/SpotifyIntegration/PlayListSynchronizer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
 | 
			
		||||
public class PlayListSynchronizer
 | 
			
		||||
{
 | 
			
		||||
    private SpotifyApiClient _spotifyAPIClient;
 | 
			
		||||
 | 
			
		||||
    public PlayListSynchronizer(SpotifyApiClient spotifyAPIClient)
 | 
			
		||||
    {
 | 
			
		||||
        _spotifyAPIClient = spotifyAPIClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task SynchronizePlaylistAsync(SmartPlaylistDefinition playlist)
 | 
			
		||||
    {
 | 
			
		||||
        var songsToInclude = new List<Song>();
 | 
			
		||||
        using (var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            songsToInclude.AddRange(dci.SongSuggestions.Where(ss => ss.HasUsedSuggestion && playlist.Categories.Contains(ss.SuggestionHelper) && !songsToInclude.Contains(ss.Song)).Select(ss => ss.Song));
 | 
			
		||||
            songsToInclude.AddRange(playlist.ExplicitlyIncludedSongs.Where(ss => !songsToInclude.Contains(ss)));
 | 
			
		||||
            if (playlist.IncludesUnCategorizedSongs)
 | 
			
		||||
            {
 | 
			
		||||
                songsToInclude.AddRange(dci.SongSuggestions.Where(ss => !ss.HasUsedSuggestion && !songsToInclude.Contains(ss.Song)).Select(ss => ss.Song));
 | 
			
		||||
            }
 | 
			
		||||
            if (playlist.IncludesLikedSongs)
 | 
			
		||||
            {
 | 
			
		||||
                var userWithLikes = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == playlist.CreatedBy.UserId).SingleOrDefault();
 | 
			
		||||
                var likedSongs = userWithLikes.LikedSongs;
 | 
			
		||||
                songsToInclude.AddRange(likedSongs.Where(s => !songsToInclude.Contains(s)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        songsToInclude.RemoveAll(song => playlist.ExplicitlyExcludedSongs.Contains(song));
 | 
			
		||||
        var spotifyIdsToInclude = songsToInclude.Select(s => s.SpotifyId);
 | 
			
		||||
 | 
			
		||||
        var apiClient = await _spotifyAPIClient.WithUserAuthorizationAsync(playlist.CreatedBy);
 | 
			
		||||
 | 
			
		||||
        var songsAlreadyInPlaylist = await apiClient.GetSongsInPlaylist(playlist.SpotifyPlaylistId);
 | 
			
		||||
 | 
			
		||||
        var songsToAdd = spotifyIdsToInclude.Where(sti => !songsAlreadyInPlaylist.Contains(sti)).ToList();
 | 
			
		||||
        var songsToRemove = songsAlreadyInPlaylist.Where(sai => !spotifyIdsToInclude.Contains(sai)).ToList();
 | 
			
		||||
 | 
			
		||||
        apiClient.AddSongsToPlaylist(playlist.SpotifyPlaylistId, songsToAdd);
 | 
			
		||||
        apiClient.RemoveSongsFromPlaylist(playlist.SpotifyPlaylistId, songsToRemove);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task SynchronizePlaylistsAsync(IList<SmartPlaylistDefinition> playlists)
 | 
			
		||||
    {
 | 
			
		||||
        foreach(var playlist in playlists)
 | 
			
		||||
        {
 | 
			
		||||
            await SynchronizePlaylistAsync(playlist);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task SynchronizeUserPlaylistsAsync(User user)
 | 
			
		||||
    {
 | 
			
		||||
        using(var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var userPlayLists = dci.SmartPlaylistDefinitions
 | 
			
		||||
                .Include(pl => pl.ExplicitlyIncludedSongs)
 | 
			
		||||
                .Include(pl => pl.ExplicitlyExcludedSongs)
 | 
			
		||||
                .Include(pl => pl.Categories)
 | 
			
		||||
                .Include(pl => pl.CreatedBy)
 | 
			
		||||
                .Where(pl => pl.CreatedBy == user).ToList();
 | 
			
		||||
            await SynchronizePlaylistsAsync(userPlayLists);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										287
									
								
								song_of_the_day/SpotifyIntegration/SpotifyApiClient.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								song_of_the_day/SpotifyIntegration/SpotifyApiClient.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,287 @@
 | 
			
		||||
using SpotifyAPI.Web;
 | 
			
		||||
using System.Web;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
 | 
			
		||||
public class SpotifyApiClient
 | 
			
		||||
{
 | 
			
		||||
    private SpotifyClient _spotifyClient;
 | 
			
		||||
    private SpotifyClient _userAuthorizedSpotifyClient;
 | 
			
		||||
    private ILogger<SpotifyApiClient> _logger;
 | 
			
		||||
 | 
			
		||||
    public SpotifyApiClient(ILogger<SpotifyApiClient> logger)
 | 
			
		||||
    {
 | 
			
		||||
        var config = SpotifyClientConfig.CreateDefault()
 | 
			
		||||
            .WithAuthenticator(new ClientCredentialsAuthenticator(
 | 
			
		||||
                AppConfiguration.Instance.SpotifyClientId,
 | 
			
		||||
                AppConfiguration.Instance.SpotifyClientSecret));
 | 
			
		||||
 | 
			
		||||
        _spotifyClient = new SpotifyClient(config);
 | 
			
		||||
        _userAuthorizedSpotifyClient = null;
 | 
			
		||||
        _logger = logger;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<SpotifyApiClient> WithUserAuthorizationAsync(User user)
 | 
			
		||||
    {
 | 
			
		||||
        var refreshResponse = await new OAuthClient().RequestToken(
 | 
			
		||||
            new AuthorizationCodeRefreshRequest(
 | 
			
		||||
                            AppConfiguration.Instance.SpotifyClientId,
 | 
			
		||||
                            AppConfiguration.Instance.SpotifyClientSecret, 
 | 
			
		||||
                            user.SpotifyAuthRefreshToken)
 | 
			
		||||
        );
 | 
			
		||||
        var config = SpotifyClientConfig
 | 
			
		||||
            .CreateDefault()
 | 
			
		||||
            .WithAuthenticator(new AuthorizationCodeAuthenticator(
 | 
			
		||||
                            AppConfiguration.Instance.SpotifyClientId,
 | 
			
		||||
                            AppConfiguration.Instance.SpotifyClientSecret,
 | 
			
		||||
                            new AuthorizationCodeTokenResponse() {
 | 
			
		||||
                                RefreshToken = refreshResponse.RefreshToken,
 | 
			
		||||
                                AccessToken = refreshResponse.AccessToken,
 | 
			
		||||
                                TokenType = refreshResponse.TokenType,
 | 
			
		||||
                                ExpiresIn = refreshResponse.ExpiresIn,
 | 
			
		||||
                                Scope = refreshResponse.Scope,
 | 
			
		||||
                                CreatedAt = refreshResponse.CreatedAt
 | 
			
		||||
                            }));
 | 
			
		||||
 | 
			
		||||
        _userAuthorizedSpotifyClient = new SpotifyClient(config);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SpotifyClient UserAuthorizedSpotifyClient {
 | 
			
		||||
        get {
 | 
			
		||||
            if (_userAuthorizedSpotifyClient == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new Exception("Cannot perform Spotify API call without user authorization. Authorize Spotify access from your user page first!");
 | 
			
		||||
            }
 | 
			
		||||
            return _userAuthorizedSpotifyClient;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<List<FullTrack>> GetTrackCandidatesAsync(string trackName, string artistName)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var searchResponse = await _spotifyClient.Search.Item(new SearchRequest(SearchRequest.Types.Track, $"{trackName} {artistName}")
 | 
			
		||||
            {
 | 
			
		||||
                Limit = 5
 | 
			
		||||
            });
 | 
			
		||||
            return searchResponse.Tracks.Items ?? new List<FullTrack>();
 | 
			
		||||
        }
 | 
			
		||||
        catch (APIException ex)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Error fetching tracks by query: \"{trackName} {artistName}\": {ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public string GetLoginRedirectUri()
 | 
			
		||||
    {
 | 
			
		||||
        return "http://127.0.0.1:5000/SpotifyLogin";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private bool IsAuthTokenExpired(User user)
 | 
			
		||||
    {
 | 
			
		||||
        if (user.SpotifyAuthCreatedAt == null || user.SpotifyAuthExpiresAfterSeconds == null)
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        var expirationdate = user.SpotifyAuthCreatedAt.Value.AddSeconds(user.SpotifyAuthExpiresAfterSeconds.Value);
 | 
			
		||||
        return expirationdate < DateTime.UtcNow;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task DeAuthorizeUserAsync(User user)
 | 
			
		||||
    {
 | 
			
		||||
        using(var dci = DataContext.Instance)
 | 
			
		||||
        {
 | 
			
		||||
            var isEntityTracked = dci.Entry(user).State != EntityState.Detached;
 | 
			
		||||
 | 
			
		||||
            if(!isEntityTracked)
 | 
			
		||||
            {
 | 
			
		||||
                user = dci.Users.Find(user.UserId); 
 | 
			
		||||
            }
 | 
			
		||||
            user.SpotifyAuthAccessToken = string.Empty;
 | 
			
		||||
            user.SpotifyAuthRefreshToken = string.Empty;
 | 
			
		||||
            user.SpotifyAuthExpiresAfterSeconds = null;
 | 
			
		||||
            user.SpotifyAuthCreatedAt = null;
 | 
			
		||||
            await dci.SaveChangesAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<string> GetValidAuthorizationTokenAsync(User user)
 | 
			
		||||
    {
 | 
			
		||||
        if(string.IsNullOrEmpty(user.SpotifyAuthAccessToken))
 | 
			
		||||
        {
 | 
			
		||||
            // user either never connected Spotify or we failed to refresh token - user needs to re-authenticate
 | 
			
		||||
            return string.Empty;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!this.IsAuthTokenExpired(user))
 | 
			
		||||
        {
 | 
			
		||||
            return user.SpotifyAuthAccessToken;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // if token is expired, attempt a refresh
 | 
			
		||||
        var dci = DataContext.Instance;
 | 
			
		||||
 | 
			
		||||
        var isEntityTracked = dci.Entry(user).State != EntityState.Detached;
 | 
			
		||||
 | 
			
		||||
        if(!isEntityTracked)
 | 
			
		||||
        {
 | 
			
		||||
            user = dci.Users.Find(user.UserId); 
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try  
 | 
			
		||||
        {
 | 
			
		||||
            var oAuthResponse = await new OAuthClient().RequestToken(
 | 
			
		||||
                new AuthorizationCodeRefreshRequest(AppConfiguration.Instance.SpotifyClientId, AppConfiguration.Instance.SpotifyClientSecret, user.SpotifyAuthRefreshToken)
 | 
			
		||||
            );
 | 
			
		||||
            user.SpotifyAuthAccessToken = oAuthResponse.AccessToken;
 | 
			
		||||
            user.SpotifyAuthExpiresAfterSeconds = oAuthResponse.ExpiresIn;
 | 
			
		||||
            user.SpotifyAuthCreatedAt = oAuthResponse.CreatedAt;
 | 
			
		||||
            user.SpotifyAuthRefreshToken = oAuthResponse.RefreshToken;
 | 
			
		||||
            return user.SpotifyAuthAccessToken;
 | 
			
		||||
        }
 | 
			
		||||
        catch(Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.LogWarning($"Failed to refresh SpotifyAuth token for user {user.LdapUserName}: {ex.Message}");
 | 
			
		||||
            await DeAuthorizeUserAsync(user);
 | 
			
		||||
            return string.Empty;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            await dci.SaveChangesAsync();
 | 
			
		||||
            dci.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> IsUserAuthenticatedAsync(User user)
 | 
			
		||||
    {
 | 
			
		||||
        return !string.IsNullOrEmpty(await this.GetValidAuthorizationTokenAsync(user));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<FullTrack> GetTrackByIdAsync(string trackId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            return await _spotifyClient.Tracks.Get(trackId);
 | 
			
		||||
        }
 | 
			
		||||
        catch (APIException ex)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Error fetching track by ID: {trackId}: {ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<SmartPlaylistDefinition> CreateSpotifyPlaylist(string playlistTitle,
 | 
			
		||||
                                                                    string description,
 | 
			
		||||
                                                                    bool IncludesUnCategorizedSongs,
 | 
			
		||||
                                                                    bool IncludesLikedSongs,
 | 
			
		||||
                                                                    User createdBy)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // for now hardcoded with my user ID
 | 
			
		||||
            var playlistCreationRequest = new PlaylistCreateRequest(playlistTitle);
 | 
			
		||||
            playlistCreationRequest.Public = true;
 | 
			
		||||
            playlistCreationRequest.Collaborative = false;
 | 
			
		||||
            playlistCreationRequest.Description = description;
 | 
			
		||||
            var currentUser = await UserAuthorizedSpotifyClient.UserProfile.Current();
 | 
			
		||||
            var playlist = await UserAuthorizedSpotifyClient.Playlists.Create(currentUser.Id, playlistCreationRequest);
 | 
			
		||||
 | 
			
		||||
            _logger.LogWarning($"Creating new playlist '{playlistTitle}'");
 | 
			
		||||
            using(var dci = DataContext.Instance)
 | 
			
		||||
            {
 | 
			
		||||
                var trackedUserEntity = dci.Users.Find(createdBy.UserId);
 | 
			
		||||
                var newPlaylist = new SmartPlaylistDefinition()
 | 
			
		||||
                {
 | 
			
		||||
                    Title = playlistTitle,
 | 
			
		||||
                    Description = description,
 | 
			
		||||
                    Categories = new List<SuggestionHelper>(),
 | 
			
		||||
                    IncludesLikedSongs = IncludesLikedSongs,
 | 
			
		||||
                    IncludesUnCategorizedSongs = IncludesUnCategorizedSongs,
 | 
			
		||||
                    SpotifyPlaylistId = playlist.Id,
 | 
			
		||||
                    CreatedBy = trackedUserEntity,
 | 
			
		||||
                    ExplicitlyExcludedSongs = new List<Song>(),
 | 
			
		||||
                    ExplicitlyIncludedSongs = new List<Song>()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                var trackedEntity = dci.SmartPlaylistDefinitions.Add(newPlaylist);
 | 
			
		||||
                await dci.SaveChangesAsync();
 | 
			
		||||
                return trackedEntity.Entity;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (APIException ex)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Error creating playlist with title: {playlistTitle}: {ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<List<string>> GetSongsInPlaylist(string playlistId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // for now hardcoded with my user ID
 | 
			
		||||
            var ids = new List<string>();
 | 
			
		||||
            var firstContentPage = await UserAuthorizedSpotifyClient.Playlists.GetItems(playlistId);
 | 
			
		||||
            var allPages = await UserAuthorizedSpotifyClient.PaginateAll(firstContentPage);
 | 
			
		||||
            ids.AddRange(allPages.Select(track => (track.Track as FullTrack).Id));
 | 
			
		||||
 | 
			
		||||
            return ids;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        catch (APIException ex)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Error fetching playlist contents for playlist with id: {playlistId}: {ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<string> AddSongsToPlaylist(string playlistId, List<string> songIds)
 | 
			
		||||
    {
 | 
			
		||||
        if (songIds.Count == 0)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.LogWarning($"No songs to add to playlist with id '{playlistId}'");
 | 
			
		||||
            return string.Empty;
 | 
			
		||||
        }
 | 
			
		||||
        try
 | 
			
		||||
            {
 | 
			
		||||
                // for now hardcoded with my user ID
 | 
			
		||||
                var addItemRequest = new PlaylistAddItemsRequest(songIds.Select(id => $"spotify:track:{id}").ToList());
 | 
			
		||||
                _logger.LogWarning($"Adding songs to playlist with id '{playlistId}'");
 | 
			
		||||
                var response = await UserAuthorizedSpotifyClient.Playlists.AddItems(playlistId, addItemRequest);
 | 
			
		||||
                return response.SnapshotId;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            catch (APIException ex)
 | 
			
		||||
            {
 | 
			
		||||
                throw new Exception($"Error adding songs to playlist with id: {playlistId}: {ex.Message}", ex);
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<string> RemoveSongsFromPlaylist(string playlistId, List<string> songIds)
 | 
			
		||||
    {
 | 
			
		||||
        if (songIds.Count == 0)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.LogWarning($"No songs to remove from playlist with id '{playlistId}'");
 | 
			
		||||
            return string.Empty;
 | 
			
		||||
        }
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // for now hardcoded with my user ID
 | 
			
		||||
            var removeItemsRequest = new PlaylistRemoveItemsRequest();
 | 
			
		||||
            removeItemsRequest.Tracks = new List<PlaylistRemoveItemsRequest.Item>();
 | 
			
		||||
            foreach (var song in songIds)
 | 
			
		||||
            {
 | 
			
		||||
                var item = new PlaylistRemoveItemsRequest.Item()
 | 
			
		||||
                {
 | 
			
		||||
                    Uri = $"spotify:track:{song}",
 | 
			
		||||
                };
 | 
			
		||||
                removeItemsRequest.Tracks.Add(item);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            _logger.LogWarning($"Removing songs from playlist with id '{playlistId}'");
 | 
			
		||||
            var response = await UserAuthorizedSpotifyClient.Playlists.RemoveItems(playlistId, removeItemsRequest);
 | 
			
		||||
            return response.SnapshotId;
 | 
			
		||||
        }
 | 
			
		||||
        catch (APIException ex)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Error removing songs from playlist with id: {playlistId}: {ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
using SpotifyAPI.Web;
 | 
			
		||||
 | 
			
		||||
public class SpotifyApiClient
 | 
			
		||||
{
 | 
			
		||||
    private SpotifyClient _spotifyClient;
 | 
			
		||||
 | 
			
		||||
    public SpotifyApiClient()
 | 
			
		||||
    {
 | 
			
		||||
        var config = SpotifyClientConfig.CreateDefault()
 | 
			
		||||
            .WithAuthenticator(new ClientCredentialsAuthenticator(
 | 
			
		||||
                AppConfiguration.Instance.SpotifyClientId,
 | 
			
		||||
                AppConfiguration.Instance.SpotifyClientSecret));
 | 
			
		||||
 | 
			
		||||
        _spotifyClient = new SpotifyClient(config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<List<FullTrack>> GetTrackCandidatesAsync(string trackName, string artistName)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var searchResponse = await _spotifyClient.Search.Item(new SearchRequest(SearchRequest.Types.Track, $"{trackName} {artistName}")
 | 
			
		||||
            {
 | 
			
		||||
                Limit = 5
 | 
			
		||||
            });
 | 
			
		||||
            return searchResponse.Tracks.Items ?? new List<FullTrack>();
 | 
			
		||||
        }
 | 
			
		||||
        catch (APIException ex)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Error fetching tracks by query: \"{trackName} {artistName}\": {ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<FullTrack> GetTrackByIdAsync(string trackId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            return await _spotifyClient.Tracks.Get(trackId);
 | 
			
		||||
        }
 | 
			
		||||
        catch (APIException ex)
 | 
			
		||||
        {
 | 
			
		||||
            throw new Exception($"Error fetching track by ID: {trackId}: {ex.Message}", ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user