From bfd185906c871c3ebf11b87d21bb488f18409f87 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:19:40 -0700 Subject: [PATCH 01/16] Context was updated to support identity --- Newsbot.Collector.Database/DatabaseContext.cs | 55 ++++++++++++------- .../Newsbot.Collector.Database.csproj | 1 + 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/Newsbot.Collector.Database/DatabaseContext.cs b/Newsbot.Collector.Database/DatabaseContext.cs index d48c067..c4d8710 100644 --- a/Newsbot.Collector.Database/DatabaseContext.cs +++ b/Newsbot.Collector.Database/DatabaseContext.cs @@ -1,3 +1,5 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newsbot.Collector.Domain.Consts; @@ -5,7 +7,7 @@ using Newsbot.Collector.Domain.Entities; namespace Newsbot.Collector.Database; -public class DatabaseContext : DbContext +public class DatabaseContext : IdentityDbContext { public DbSet Articles { get; set; } = null!; public DbSet DiscordQueue { get; set; } = null!; @@ -13,38 +15,51 @@ public class DatabaseContext : DbContext public DbSet Icons { get; set; } = null!; public DbSet Sources { get; set; } = null!; public DbSet Subscriptions { get; set; } = null!; - - private string ConnectionString { get; set; } - public DatabaseContext(IConfiguration appsettings, string connectionString) - { - var connString = appsettings.GetConnectionString(ConfigConnectionStringConst.Database); - ConnectionString = connString ?? ""; - } + //public DbSet Users { get; set; } = null!; + + private string ConnectionString { get; set; } = ""; + + //public DatabaseContext(IConfiguration appsettings, string connectionString) + //{ + // var connString = appsettings.GetConnectionString(ConfigConnectionStringConst.Database); + // ConnectionString = connString ?? ""; + //} public DatabaseContext(string connectionString) { ConnectionString = connectionString; } - public DatabaseContext(DbContextOptions connectionString) - { - ConnectionString = ""; - } - - public DatabaseContext() - { - ConnectionString = ""; - } - protected override void OnConfiguring(DbContextOptionsBuilder options) { - options.UseNpgsql(ConnectionString); + if (ConnectionString != "") + { + options.UseNpgsql(ConnectionString); + } + } + + //public DatabaseContext(DbContextOptions connectionString) + //{ + // ConnectionString = ""; + //} + + //public DatabaseContext() + //{ + // ConnectionString = ""; + //} + + + public DatabaseContext(DbContextOptions options) + : base(options) + { + //ConnectionString = ""; + } public DatabaseContext(DbContextOptions options, string connectionString) : base(options) { - ConnectionString = connectionString; + //ConnectionString = connectionString; } } \ No newline at end of file diff --git a/Newsbot.Collector.Database/Newsbot.Collector.Database.csproj b/Newsbot.Collector.Database/Newsbot.Collector.Database.csproj index efbaba5..68f1436 100644 --- a/Newsbot.Collector.Database/Newsbot.Collector.Database.csproj +++ b/Newsbot.Collector.Database/Newsbot.Collector.Database.csproj @@ -6,6 +6,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive From d15ae49ff8b6d6623cb42048c3543bbc0e60d804 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:20:13 -0700 Subject: [PATCH 02/16] Minor config update to add a new namespace --- .../Models/Config/{ => Sources}/ConfigSectionRedditModel.cs | 2 +- .../Models/Config/{ => Sources}/ConfigSectionRssModel.cs | 2 +- .../Models/Config/{ => Sources}/ConfigSectionYoutubeModel.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename Newsbot.Collector.Domain/Models/Config/{ => Sources}/ConfigSectionRedditModel.cs (77%) rename Newsbot.Collector.Domain/Models/Config/{ => Sources}/ConfigSectionRssModel.cs (57%) rename Newsbot.Collector.Domain/Models/Config/{ => Sources}/ConfigSectionYoutubeModel.cs (67%) diff --git a/Newsbot.Collector.Domain/Models/Config/ConfigSectionRedditModel.cs b/Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionRedditModel.cs similarity index 77% rename from Newsbot.Collector.Domain/Models/Config/ConfigSectionRedditModel.cs rename to Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionRedditModel.cs index b33f56b..e647630 100644 --- a/Newsbot.Collector.Domain/Models/Config/ConfigSectionRedditModel.cs +++ b/Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionRedditModel.cs @@ -1,4 +1,4 @@ -namespace Newsbot.Collector.Domain.Models.Config; +namespace Newsbot.Collector.Domain.Models.Config.Sources; public class ConfigSectionRedditModel { diff --git a/Newsbot.Collector.Domain/Models/Config/ConfigSectionRssModel.cs b/Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionRssModel.cs similarity index 57% rename from Newsbot.Collector.Domain/Models/Config/ConfigSectionRssModel.cs rename to Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionRssModel.cs index 597d14d..e2afef3 100644 --- a/Newsbot.Collector.Domain/Models/Config/ConfigSectionRssModel.cs +++ b/Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionRssModel.cs @@ -1,4 +1,4 @@ -namespace Newsbot.Collector.Domain.Models.Config; +namespace Newsbot.Collector.Domain.Models.Config.Sources; public class ConfigSectionRssModel { diff --git a/Newsbot.Collector.Domain/Models/Config/ConfigSectionYoutubeModel.cs b/Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionYoutubeModel.cs similarity index 67% rename from Newsbot.Collector.Domain/Models/Config/ConfigSectionYoutubeModel.cs rename to Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionYoutubeModel.cs index b6cb788..bdc52b0 100644 --- a/Newsbot.Collector.Domain/Models/Config/ConfigSectionYoutubeModel.cs +++ b/Newsbot.Collector.Domain/Models/Config/Sources/ConfigSectionYoutubeModel.cs @@ -1,4 +1,4 @@ -namespace Newsbot.Collector.Domain.Models.Config; +namespace Newsbot.Collector.Domain.Models.Config.Sources; public class ConfigSectionYoutubeModel { From 9d5c5903bd9a73347c21f99ee3c59a1eefe23b71 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:21:26 -0700 Subject: [PATCH 03/16] Migrations have been added with identity support --- .../20230629222603_identity.Designer.cs | 470 ++++++++++++++++++ .../Migrations/20230629222603_identity.cs | 223 +++++++++ .../DatabaseContextModelSnapshot.cs | 249 +++++++++- 3 files changed, 941 insertions(+), 1 deletion(-) create mode 100644 Newsbot.Collector.Database/Migrations/20230629222603_identity.Designer.cs create mode 100644 Newsbot.Collector.Database/Migrations/20230629222603_identity.cs diff --git a/Newsbot.Collector.Database/Migrations/20230629222603_identity.Designer.cs b/Newsbot.Collector.Database/Migrations/20230629222603_identity.Designer.cs new file mode 100644 index 0000000..422d205 --- /dev/null +++ b/Newsbot.Collector.Database/Migrations/20230629222603_identity.Designer.cs @@ -0,0 +1,470 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Newsbot.Collector.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Newsbot.Collector.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230629222603_identity")] + partial class identity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorImage") + .HasColumnType("text"); + + b.Property("AuthorName") + .IsRequired() + .HasColumnType("text"); + + b.Property("CodeIsCommit") + .HasColumnType("boolean"); + + b.Property("CodeIsRelease") + .HasColumnType("boolean"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("PubDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Thumbnail") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .HasColumnType("text"); + + b.Property("Video") + .IsRequired() + .HasColumnType("text"); + + b.Property("VideoHeight") + .HasColumnType("integer"); + + b.Property("VideoWidth") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Articles"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArticleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("DiscordQueue"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Server") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DiscordWebhooks"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Icons"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Deleted") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("YoutubeId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Sources"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CodeAllowCommits") + .HasColumnType("boolean"); + + b.Property("CodeAllowReleases") + .HasColumnType("boolean"); + + b.Property("DiscordWebHookId") + .HasColumnType("uuid"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Newsbot.Collector.Database/Migrations/20230629222603_identity.cs b/Newsbot.Collector.Database/Migrations/20230629222603_identity.cs new file mode 100644 index 0000000..4ad54d5 --- /dev/null +++ b/Newsbot.Collector.Database/Migrations/20230629222603_identity.cs @@ -0,0 +1,223 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Newsbot.Collector.Database.Migrations +{ + /// + public partial class identity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "text", nullable: false), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "text", nullable: false), + ProviderKey = table.Column(type: "text", nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs b/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs index a2c6ecb..56817b6 100644 --- a/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs @@ -17,11 +17,207 @@ namespace Newsbot.Collector.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.7") + .HasAnnotation("ProductVersion", "7.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b => { b.Property("Id") @@ -214,6 +410,57 @@ namespace Newsbot.Collector.Database.Migrations b.ToTable("Subscriptions"); }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); #pragma warning restore 612, 618 } } From f388d642be1ee22ef04a9c0f40985ce95a0d5c3b Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:21:53 -0700 Subject: [PATCH 04/16] Tables have been updated to support databaseContext or connection string passed in --- .../Repositories/ArticlesTable.cs | 65 ++++++++------- .../Repositories/DiscordQueue.cs | 37 +++++---- .../Repositories/DiscordWebhooksTable.cs | 61 +++++++------- .../Repositories/IconsTable.cs | 38 ++++----- .../Repositories/SourcesTable.cs | 82 +++++++++---------- .../Repositories/SubscriptionsTable.cs | 59 +++++++------ 6 files changed, 178 insertions(+), 164 deletions(-) diff --git a/Newsbot.Collector.Database/Repositories/ArticlesTable.cs b/Newsbot.Collector.Database/Repositories/ArticlesTable.cs index d75ecf5..6a383ff 100644 --- a/Newsbot.Collector.Database/Repositories/ArticlesTable.cs +++ b/Newsbot.Collector.Database/Repositories/ArticlesTable.cs @@ -11,29 +11,34 @@ namespace Newsbot.Collector.Database.Repositories; public class ArticlesTable : IArticlesRepository { - private readonly string _connectionString; + //private readonly string _connectionString; private DatabaseContext _context; public ArticlesTable(string connectionString) { - _connectionString = connectionString; + //_connectionString = connectionString; _context = new DatabaseContext(connectionString); } - public ArticlesTable(IConfiguration configuration) + public ArticlesTable(DatabaseContext context) { - var conn = configuration.GetConnectionString("database"); - if (conn is null) conn = ""; - - _connectionString = conn; - _context = new DatabaseContext(conn); + _context = context; } + //public ArticlesTable(IConfiguration configuration) + //{ + // var conn = configuration.GetConnectionString("database"); + // if (conn is null) conn = ""; + // + // _context = new DatabaseContext(conn); + //} + public List List(int page = 0, int count = 25) { - using var context = new DatabaseContext(_connectionString); - var query = context.Articles + //using var context = new DatabaseContext(_connectionString); + + var query = _context.Articles .Skip(page * count) .OrderBy(d => d.PubDate) .Take(25); @@ -43,8 +48,8 @@ public class ArticlesTable : IArticlesRepository public ArticlesEntity GetById(Guid id) { - using var context = new DatabaseContext(_connectionString); - var query = context.Articles + //using var context = new DatabaseContext(_connectionString); + var query = _context.Articles .FirstOrDefault(d => d.Id.Equals(id)); query ??= new ArticlesEntity(); return query; @@ -52,16 +57,16 @@ public class ArticlesTable : IArticlesRepository public ArticlesEntity GetByUrl(string url) { - using var context = new DatabaseContext(_connectionString); - var res = context.Articles.FirstOrDefault(d => d.Url!.Equals(url)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.Articles.FirstOrDefault(d => d.Url!.Equals(url)); res ??= new ArticlesEntity(); return res; } public List ListBySourceId(Guid id, int page, int count) { - using var context = new DatabaseContext(_connectionString); - var res = context.Articles + //using var context = new DatabaseContext(_connectionString); + var res = _context.Articles .Skip(page * count) .Where(d => d.SourceId.Equals(id)); return res.ToList(); @@ -69,13 +74,14 @@ public class ArticlesTable : IArticlesRepository public ArticlesEntity New(ArticlesEntity model) { - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); model.Id = new Guid(); - var query = context.Articles.Add(model); - context.SaveChanges(); + var query = _context.Articles.Add(model); + _context.SaveChanges(); return model; } + /* public ArticlesModel NewDapper(ArticlesModel model) { model.ID = Guid.NewGuid(); @@ -103,26 +109,27 @@ public class ArticlesTable : IArticlesRepository }); return model; } + */ public void DeleteAllBySourceId(Guid sourceId) { - using var context = new DatabaseContext(_connectionString); - var res = context.Articles + //using var context = new DatabaseContext(_connectionString); + var res = _context.Articles .Where(d => d.SourceId.Equals(sourceId)) .ToList(); foreach (var item in res) { - context.Articles.Remove(item); + _context.Articles.Remove(item); } - context.SaveChanges(); + _context.SaveChanges(); } - private IDbConnection OpenConnection(string connectionString) - { - var conn = new NpgsqlConnection(_connectionString); - conn.Open(); - return conn; - } + //private IDbConnection OpenConnection(string connectionString) + //{ + // //var conn = new NpgsqlConnection(_connectionString); + // //conn.Open(); + // //return conn; + //} } \ No newline at end of file diff --git a/Newsbot.Collector.Database/Repositories/DiscordQueue.cs b/Newsbot.Collector.Database/Repositories/DiscordQueue.cs index ad77070..2cf95c3 100644 --- a/Newsbot.Collector.Database/Repositories/DiscordQueue.cs +++ b/Newsbot.Collector.Database/Repositories/DiscordQueue.cs @@ -9,42 +9,49 @@ namespace Newsbot.Collector.Database.Repositories; public class DiscordQueueTable : IDiscordQueueRepository { - private string _connectionString; + //private string _connectionString; + private DatabaseContext _context; public DiscordQueueTable(string connectionString) { - _connectionString = connectionString; + //_connectionString = connectionString; + _context = new DatabaseContext(connectionString); } - private IDbConnection OpenConnection(string connectionString) + public DiscordQueueTable(DatabaseContext context) { - var conn = new NpgsqlConnection(_connectionString); - conn.Open(); - return conn; + _context = context; } + //private IDbConnection OpenConnection(string connectionString) + //{ + // var conn = new NpgsqlConnection(_connectionString); + // conn.Open(); + // return conn; + //} + public void New(DiscordQueueEntity model) { model.Id = new Guid(); - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordQueue.Add(model); - context.SaveChanges(); + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordQueue.Add(model); + _context.SaveChanges(); } public void Delete(Guid id) { - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordQueue.FirstOrDefault(d => d.Id.Equals(id)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordQueue.FirstOrDefault(d => d.Id.Equals(id)); res ??= new DiscordQueueEntity(); - context.DiscordQueue.Remove(res); - context.SaveChanges(); + _context.DiscordQueue.Remove(res); + _context.SaveChanges(); } public List List(int limit = 25) { - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordQueue.Take(limit).ToList(); + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordQueue.Take(limit).ToList(); return res; } } \ No newline at end of file diff --git a/Newsbot.Collector.Database/Repositories/DiscordWebhooksTable.cs b/Newsbot.Collector.Database/Repositories/DiscordWebhooksTable.cs index 58aac8a..c667d23 100644 --- a/Newsbot.Collector.Database/Repositories/DiscordWebhooksTable.cs +++ b/Newsbot.Collector.Database/Repositories/DiscordWebhooksTable.cs @@ -9,49 +9,50 @@ namespace Newsbot.Collector.Database.Repositories; public class DiscordWebhooksTable : IDiscordWebHooksRepository { - private readonly string _connectionString; + //private readonly string _connectionString; + private DatabaseContext _context; public DiscordWebhooksTable(string connectionString) { - _connectionString = connectionString; + //_connectionString = connectionString; + _context = new DatabaseContext(connectionString); } - public DiscordWebhooksTable(IConfiguration configuration) + public DiscordWebhooksTable(DatabaseContext context) { - var connstr = configuration.GetConnectionString("database") ?? ""; - _connectionString = connstr; + _context = context; } public DiscordWebhookEntity New(DiscordWebhookEntity model) { model.Id = new Guid(); - using var context = new DatabaseContext(_connectionString); - context.DiscordWebhooks.Add(model); - context.SaveChanges(); + //using var context = new DatabaseContext(_connectionString); + _context.DiscordWebhooks.Add(model); + _context.SaveChanges(); return model; } public DiscordWebhookEntity GetById(Guid id) { - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordWebhooks.FirstOrDefault(d => d.Id.Equals(id)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordWebhooks.FirstOrDefault(d => d.Id.Equals(id)); res ??= new DiscordWebhookEntity(); return res; } public DiscordWebhookEntity GetByUrl(string url) { - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordWebhooks.FirstOrDefault(d => d.Url.Equals(url)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordWebhooks.FirstOrDefault(d => d.Url.Equals(url)); res ??= new DiscordWebhookEntity(); return res; } public List List(int page, int count = 25) { - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordWebhooks + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordWebhooks .Skip(page * count) .Take(count) .ToList(); @@ -61,8 +62,8 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository public List ListByServer(string server, int limit = 25) { - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordWebhooks + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordWebhooks .Where(d => d.Server.Equals(server)) .Take(limit) .ToList(); @@ -72,8 +73,8 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository public List ListByServerAndChannel(string server, string channel, int limit = 25) { - using var context = new DatabaseContext(_connectionString); - var res = context.DiscordWebhooks + //using var context = new DatabaseContext(_connectionString); + var res = _context.DiscordWebhooks .Where(s => s.Server.Equals(server)) .Where(c => c.Channel.Equals(channel)) .Take(limit) @@ -85,14 +86,14 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository public int Disable(Guid id) { var res = GetById(id); - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); res.Enabled = true; - context.DiscordWebhooks.Update(res); + _context.DiscordWebhooks.Update(res); try { - context.SaveChanges(); + _context.SaveChanges(); return 1; } catch(Exception ex) @@ -105,14 +106,14 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository public int Enable(Guid id) { var res = GetById(id); - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); res.Enabled = false; - context.DiscordWebhooks.Update(res); + _context.DiscordWebhooks.Update(res); try { - context.SaveChanges(); + _context.SaveChanges(); return 1; } catch(Exception ex) @@ -122,10 +123,10 @@ public class DiscordWebhooksTable : IDiscordWebHooksRepository } } - private IDbConnection OpenConnection(string connectionString) - { - var conn = new NpgsqlConnection(_connectionString); - conn.Open(); - return conn; - } + //private IDbConnection OpenConnection(string connectionString) + //{ + // var conn = new NpgsqlConnection(_connectionString); + // conn.Open(); + // return conn; + //} } \ No newline at end of file diff --git a/Newsbot.Collector.Database/Repositories/IconsTable.cs b/Newsbot.Collector.Database/Repositories/IconsTable.cs index 1ef0c42..8f03c8b 100644 --- a/Newsbot.Collector.Database/Repositories/IconsTable.cs +++ b/Newsbot.Collector.Database/Repositories/IconsTable.cs @@ -10,49 +10,49 @@ namespace Newsbot.Collector.Database.Repositories; public class IconsTable : IIconsRepository { - private readonly string _connectionString; + //private readonly string _connectionString; + private DatabaseContext _context; public IconsTable(string connectionString) { - _connectionString = connectionString; + //_connectionString = connectionString; + _context = new DatabaseContext(connectionString); } - public IconsTable(IConfiguration configuration) + public IconsTable(DatabaseContext context) { - var connstr = configuration.GetConnectionString("database"); - if (connstr is null) connstr = ""; - _connectionString = connstr; + _context = context; } public void New(IconEntity model) { - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); model.Id = Guid.NewGuid(); - context.Icons.Add(model); - context.SaveChanges(); + _context.Icons.Add(model); + _context.SaveChanges(); } public IconEntity GetById(Guid id) { - using var context = new DatabaseContext(_connectionString); - var res = context.Icons.FirstOrDefault(f => f.Id.Equals(id)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.Icons.FirstOrDefault(f => f.Id.Equals(id)); res ??= new IconEntity(); return res; } public IconEntity GetBySourceId(Guid id) { - using var context = new DatabaseContext(_connectionString); - var res = context.Icons.FirstOrDefault(f => f.SourceId.Equals(id)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.Icons.FirstOrDefault(f => f.SourceId.Equals(id)); res ??= new IconEntity(); return res; } - private IDbConnection OpenConnection(string connectionString) - { - var conn = new NpgsqlConnection(_connectionString); - conn.Open(); - return conn; - } + //private IDbConnection OpenConnection(string connectionString) + //{ + // var conn = new NpgsqlConnection(_connectionString); + // conn.Open(); + // return conn; + //} } \ No newline at end of file diff --git a/Newsbot.Collector.Database/Repositories/SourcesTable.cs b/Newsbot.Collector.Database/Repositories/SourcesTable.cs index 09e364b..8340de0 100644 --- a/Newsbot.Collector.Database/Repositories/SourcesTable.cs +++ b/Newsbot.Collector.Database/Repositories/SourcesTable.cs @@ -10,28 +10,28 @@ namespace Newsbot.Collector.Database.Repositories; public class SourcesTable : ISourcesRepository { - private readonly string _connectionString; + //private readonly string _connectionString; + private DatabaseContext _context; public SourcesTable(string connectionString) { - _connectionString = connectionString; + //_connectionString = connectionString; + _context = new DatabaseContext(connectionString); } - public SourcesTable(IConfiguration configuration) + public SourcesTable(DatabaseContext context) { - var connstr = configuration.GetConnectionString("database"); - if (connstr is null) connstr = ""; - _connectionString = connstr; + _context = context; } public SourceEntity New(SourceEntity model) { model.Id = Guid.NewGuid(); - using var context = new DatabaseContext(_connectionString); - context.Sources.Add(model); + //using var context = new DatabaseContext(_connectionString); + _context.Sources.Add(model); try { - context.SaveChanges(); + _context.SaveChanges(); } catch (Exception ex) { @@ -43,8 +43,8 @@ public class SourcesTable : ISourcesRepository public SourceEntity GetById(Guid id) { - using var context = new DatabaseContext(_connectionString); - var res = context.Sources.FirstOrDefault(f => f.Id.Equals(id)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.Sources.FirstOrDefault(f => f.Id.Equals(id)); res ??= new SourceEntity(); return res; } @@ -57,16 +57,16 @@ public class SourcesTable : ISourcesRepository public SourceEntity GetByName(string name) { - using var context = new DatabaseContext(_connectionString); - var res = context.Sources.FirstOrDefault(f => f.Name.Equals(name)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.Sources.FirstOrDefault(f => f.Name.Equals(name)); res ??= new SourceEntity(); return res; } public SourceEntity GetByNameAndType(string name, string type) { - using var context = new DatabaseContext(_connectionString); - var res = context.Sources + //using var context = new DatabaseContext(_connectionString); + var res = _context.Sources .Where(f => f.Name.Equals(name)) .FirstOrDefault(f => f.Type.Equals(type)); res ??= new SourceEntity(); @@ -75,8 +75,8 @@ public class SourcesTable : ISourcesRepository public SourceEntity GetByUrl(string url) { - using var context = new DatabaseContext(_connectionString); - var res = context.Sources + //using var context = new DatabaseContext(_connectionString); + var res = _context.Sources .FirstOrDefault(f => f.Url.Equals(url)); res ??= new SourceEntity(); return res; @@ -84,8 +84,8 @@ public class SourcesTable : ISourcesRepository public List List(int page = 0, int count = 100) { - using var context = new DatabaseContext(_connectionString); - var res = context.Sources + //using var context = new DatabaseContext(_connectionString); + var res = _context.Sources .Skip(page * count) .Take(count) .ToList(); @@ -94,8 +94,8 @@ public class SourcesTable : ISourcesRepository public List ListBySource(string source, int page = 0, int limit = 25) { - using var context = new DatabaseContext(_connectionString); - var res = context.Sources + //using var context = new DatabaseContext(_connectionString); + var res = _context.Sources .Where(f => f.Source.Equals(source)) .Skip(page * limit) .Take(limit) @@ -105,8 +105,8 @@ public class SourcesTable : ISourcesRepository public List ListByType(string type,int page = 0, int limit = 25) { - using var context = new DatabaseContext(_connectionString); - var res = context.Sources + //using var context = new DatabaseContext(_connectionString); + var res = _context.Sources .Where(f => f.Type.Equals(type)) .Skip(page * limit) .Take(limit) @@ -116,13 +116,13 @@ public class SourcesTable : ISourcesRepository public int Disable(Guid id) { - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); var res = GetById(id); res.Enabled = false; - context.Sources.Update(res); + _context.Sources.Update(res); try { - context.SaveChanges(); + _context.SaveChanges(); return 1; } catch @@ -133,13 +133,13 @@ public class SourcesTable : ISourcesRepository public int Enable(Guid id) { - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); var res = GetById(id); res.Enabled = true; - context.Sources.Update(res); + _context.Sources.Update(res); try { - context.SaveChanges(); + _context.SaveChanges(); return 1; } catch @@ -150,21 +150,21 @@ public class SourcesTable : ISourcesRepository public void Delete(Guid id) { - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); var res = GetById(id); - context.Sources.Remove(res); - context.SaveChanges(); + _context.Sources.Remove(res); + _context.SaveChanges(); } public int UpdateYoutubeId(Guid id, string youtubeId) { - using var context = new DatabaseContext(_connectionString); + //using var context = new DatabaseContext(_connectionString); var res = GetById(id); res.YoutubeId = youtubeId; - context.Sources.Update(res); + _context.Sources.Update(res); try { - context.SaveChanges(); + _context.SaveChanges(); return 1; } catch @@ -173,10 +173,10 @@ public class SourcesTable : ISourcesRepository } } - private IDbConnection OpenConnection(string connectionString) - { - var conn = new NpgsqlConnection(_connectionString); - conn.Open(); - return conn; - } + //private IDbConnection OpenConnection(string connectionString) + //{ + // var conn = new NpgsqlConnection(_connectionString); + // conn.Open(); + // return conn; + //} } \ No newline at end of file diff --git a/Newsbot.Collector.Database/Repositories/SubscriptionsTable.cs b/Newsbot.Collector.Database/Repositories/SubscriptionsTable.cs index a4c13ad..871a6d5 100644 --- a/Newsbot.Collector.Database/Repositories/SubscriptionsTable.cs +++ b/Newsbot.Collector.Database/Repositories/SubscriptionsTable.cs @@ -10,61 +10,61 @@ namespace Newsbot.Collector.Database.Repositories; public class SubscriptionsTable : ISubscriptionRepository { - private readonly string _connectionString; + //private readonly string _connectionString; + private DatabaseContext _context; public SubscriptionsTable(string connectionString) { - _connectionString = connectionString; + //_connectionString = connectionString; + _context = new DatabaseContext(connectionString); } - public SubscriptionsTable(IConfiguration configuration) + public SubscriptionsTable(DatabaseContext context) { - var connstr = configuration.GetConnectionString("database"); - if (connstr is null) connstr = ""; - _connectionString = connstr; + _context = context; } public SubscriptionEntity New(SubscriptionEntity model) { model.Id = new Guid(); - using var context = new DatabaseContext(_connectionString); - context.Subscriptions.Add(model); - context.SaveChanges(); + //using var context = new DatabaseContext(_connectionString); + _context.Subscriptions.Add(model); + _context.SaveChanges(); return model; } public List List(int page = 0, int count = 25) { - using var context = new DatabaseContext(_connectionString); - return context.Subscriptions.Skip(page * count).Take(count).ToList(); + //using var context = new DatabaseContext(_connectionString); + return _context.Subscriptions.Skip(page * count).Take(count).ToList(); } public List ListBySourceId(Guid id, int page = 0, int count = 25) { - using var context = new DatabaseContext(_connectionString); - return context.Subscriptions.Where(f => f.SourceId.Equals(id)) + //using var context = new DatabaseContext(_connectionString); + return _context.Subscriptions.Where(f => f.SourceId.Equals(id)) .Skip(page * count) .ToList(); } public List ListByWebhook(Guid id, int page = 0, int count = 25) { - using var context = new DatabaseContext(_connectionString); - return context.Subscriptions.Where(f => f.DiscordWebHookId.Equals(id)).Skip(page * count).ToList(); + //using var context = new DatabaseContext(_connectionString); + return _context.Subscriptions.Where(f => f.DiscordWebHookId.Equals(id)).Skip(page * count).ToList(); } public SubscriptionEntity GetById(Guid id) { - using var context = new DatabaseContext(_connectionString); - var res = context.Subscriptions + //using var context = new DatabaseContext(_connectionString); + var res = _context.Subscriptions .FirstOrDefault(f => f.Id.Equals(id)); return res ??= new SubscriptionEntity(); } public SubscriptionEntity GetByWebhookAndSource(Guid webhookId, Guid sourceId) { - using var context = new DatabaseContext(_connectionString); - var res = context.Subscriptions + //using var context = new DatabaseContext(_connectionString); + var res = _context.Subscriptions .Where(f => f.DiscordWebHookId.Equals(webhookId)) .FirstOrDefault(f => f.SourceId.Equals(sourceId)); return res ??= new SubscriptionEntity(); @@ -72,22 +72,21 @@ public class SubscriptionsTable : ISubscriptionRepository public void Delete(Guid id) { - using var context = new DatabaseContext(_connectionString); - var res = context.Subscriptions.FirstOrDefault(f => f.Id.Equals(id)); + //using var context = new DatabaseContext(_connectionString); + var res = _context.Subscriptions.FirstOrDefault(f => f.Id.Equals(id)); if (res is null) { return; } - context.Subscriptions.Remove(res); - context.SaveChanges(); - + _context.Subscriptions.Remove(res); + _context.SaveChanges(); } - private IDbConnection OpenConnection(string connectionString) - { - var conn = new NpgsqlConnection(_connectionString); - conn.Open(); - return conn; - } + //private IDbConnection OpenConnection(string connectionString) + //{ + // var conn = new NpgsqlConnection(_connectionString); + // conn.Open(); + // return conn; + //} } \ No newline at end of file From f081a59229cb034f62368ac834e247cbf74c54f1 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:22:55 -0700 Subject: [PATCH 05/16] startup was updated to inject ef, tables, jwt, and update swagger with bearer token auth --- Newsbot.Collector.Api/Program.cs | 113 ++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 17 deletions(-) diff --git a/Newsbot.Collector.Api/Program.cs b/Newsbot.Collector.Api/Program.cs index 9dad4cf..ee12181 100644 --- a/Newsbot.Collector.Api/Program.cs +++ b/Newsbot.Collector.Api/Program.cs @@ -1,14 +1,24 @@ +using System.Text; using Hangfire; using Hangfire.MemoryStorage; using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Newsbot.Collector.Api; using Newsbot.Collector.Api.Authentication; +using Newsbot.Collector.Api.Services; using Newsbot.Collector.Database; +using Newsbot.Collector.Database.Repositories; using Newsbot.Collector.Domain.Consts; +using Newsbot.Collector.Domain.Entities; +using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Models; using Newsbot.Collector.Domain.Models.Config; +using Newsbot.Collector.Domain.Models.Config.Sources; using Serilog; using Serilog.Events; using ILogger = Serilog.ILogger; @@ -23,30 +33,76 @@ var config = GetConfiguration(); builder.Configuration.AddConfiguration(config); Log.Logger = GetLogger(config); - Log.Information("Starting up"); + +// configure Entity Framework +var dbconn = config.GetConnectionString("Database"); +builder.Services.AddDbContext(o => o.UseNpgsql(dbconn ?? "")); + +builder.Services.AddIdentity() + .AddRoles() + .AddEntityFrameworkStores(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +// Configure Identity +builder.Services.AddScoped(); + // Configure Hangfire builder.Services.AddHangfire(f => f.UseMemoryStorage()); builder.Services.AddHangfireServer(); GlobalConfiguration.Configuration.UseSerilogLogProvider(); +// Add Health Checks builder.Services.AddHealthChecks() .AddNpgSql(config.GetValue(ConfigConnectionStringConst.Database) ?? ""); + builder.Services.AddControllers(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.Configure(config.GetSection("ConnectionStrings")); - builder.Services.Configure(config.GetSection(ConfigSectionsConst.ConnectionStrings)); builder.Services.Configure(config.GetSection(ConfigSectionsConst.Rss)); builder.Services.Configure(config.GetSection(ConfigSectionsConst.Youtube)); //builder.Services.Configure< +// Configure JWT for auth +var jwtSettings = new JwtSettings(); +config.Bind(nameof(jwtSettings), jwtSettings); +builder.Services.AddSingleton(jwtSettings); + + +builder.Services.AddAuthentication(x => +{ + x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}).AddJwtBearer(x => +{ + x.SaveToken = true; + x.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret ?? "")), + ValidateIssuer = false, + ValidateAudience = false, + RequireExpirationTime = false, + ValidateLifetime = true + }; +}); + builder.Services.AddSwaggerGen(cfg => { + cfg.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme { Description = "The API key to access the API", @@ -56,23 +112,45 @@ builder.Services.AddSwaggerGen(cfg => Scheme = "ApiKeyScheme" }); - var scheme = new OpenApiSecurityScheme + cfg.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "ApiKey" - }, - In = ParameterLocation.Header - }; - var requirement = new OpenApiSecurityRequirement + Description = "JWT Authorization Header using the bearer scheme", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey + }); + + cfg.AddSecurityRequirement(new OpenApiSecurityRequirement { - { scheme, new List() } - }; - cfg.AddSecurityRequirement(requirement); + //{ + // new OpenApiSecurityScheme + // { + // Reference = new OpenApiReference + // { + // Type = ReferenceType.SecurityScheme, + // Id = "ApiKey" + // }, + // In = ParameterLocation.Header + // }, + // new List() + //}, + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + }, + Scheme = "oauth2", + Name = "Bearer", + In = ParameterLocation.Header + }, + new List() + } + }); }); -builder.Services.AddDbContext(); var app = builder.Build(); @@ -88,9 +166,10 @@ app.UseHttpsRedirection(); app.UseHangfireDashboard(); BackgroundJobs.SetupRecurringJobs(config); -app.UseAuthorization(); +//app.UseAuthorization(); +app.UseAuthentication(); -app.UseMiddleware(); +//app.UseMiddleware(); app.MapHealthChecks("/health", new HealthCheckOptions { From dff5556e06f99091d204324e218a29b94534f3cd Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:23:22 -0700 Subject: [PATCH 06/16] jwt package was added --- Newsbot.Collector.Api/Newsbot.Collector.Api.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj b/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj index 74cc3ae..a766358 100644 --- a/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj +++ b/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj @@ -13,6 +13,7 @@ + @@ -36,4 +37,8 @@ + + + + From 06218af516670f4b1e9c615e6f648bf3777457c6 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:24:50 -0700 Subject: [PATCH 07/16] Api domain items have been added but subject to change --- .../Domain/Requests/RegisterUserRequest.cs | 12 ++++++++++++ .../Domain/Requests/UserLoginRequest.cs | 10 ++++++++++ .../Domain/Response/AuthFailedResponse.cs | 6 ++++++ .../Domain/Response/AuthSuccessfulResponse.cs | 8 ++++++++ .../Domain/Results/AuthenticationResult.cs | 11 +++++++++++ 5 files changed, 47 insertions(+) create mode 100644 Newsbot.Collector.Api/Domain/Requests/RegisterUserRequest.cs create mode 100644 Newsbot.Collector.Api/Domain/Requests/UserLoginRequest.cs create mode 100644 Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs create mode 100644 Newsbot.Collector.Api/Domain/Response/AuthSuccessfulResponse.cs create mode 100644 Newsbot.Collector.Api/Domain/Results/AuthenticationResult.cs diff --git a/Newsbot.Collector.Api/Domain/Requests/RegisterUserRequest.cs b/Newsbot.Collector.Api/Domain/Requests/RegisterUserRequest.cs new file mode 100644 index 0000000..461615c --- /dev/null +++ b/Newsbot.Collector.Api/Domain/Requests/RegisterUserRequest.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Newsbot.Collector.Api.Domain.Requests; + +public class RegisterUserRequest +{ + //public string? Name { get; set; } + [EmailAddress] + public string? Email { get; set; } + public string? Password { get; set; } + +} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Domain/Requests/UserLoginRequest.cs b/Newsbot.Collector.Api/Domain/Requests/UserLoginRequest.cs new file mode 100644 index 0000000..8b34017 --- /dev/null +++ b/Newsbot.Collector.Api/Domain/Requests/UserLoginRequest.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Newsbot.Collector.Api.Domain.Requests; + +public class UserLoginRequest +{ + [EmailAddress] + public string? Email { get; set; } + public string? Password { get; set; } +} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs b/Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs new file mode 100644 index 0000000..891c195 --- /dev/null +++ b/Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs @@ -0,0 +1,6 @@ +namespace Newsbot.Collector.Api.Domain.Response; + +public class AuthFailedResponse +{ + public IEnumerable? Errors { get; set; } +} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Domain/Response/AuthSuccessfulResponse.cs b/Newsbot.Collector.Api/Domain/Response/AuthSuccessfulResponse.cs new file mode 100644 index 0000000..48e5009 --- /dev/null +++ b/Newsbot.Collector.Api/Domain/Response/AuthSuccessfulResponse.cs @@ -0,0 +1,8 @@ +namespace Newsbot.Collector.Api.Domain.Response; + +public class AuthSuccessfulResponse +{ + // might want to validate the user before we return the token + + public string? Token { get; set; } +} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Domain/Results/AuthenticationResult.cs b/Newsbot.Collector.Api/Domain/Results/AuthenticationResult.cs new file mode 100644 index 0000000..dfbb63b --- /dev/null +++ b/Newsbot.Collector.Api/Domain/Results/AuthenticationResult.cs @@ -0,0 +1,11 @@ +using System.Collections; + +namespace Newsbot.Collector.Api.Domain.Results; + +public class AuthenticationResult +{ + public string? Token { get; set; } + + public bool IsSuccessful { get; set; } + public IEnumerable? ErrorMessage { get; set; } +} \ No newline at end of file From 3f4de341152dc77ffbbca6bfc2aae7af06c52573 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:25:33 -0700 Subject: [PATCH 08/16] new identity service was added to assist with auth --- .../Services/IdentityService.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Newsbot.Collector.Api/Services/IdentityService.cs diff --git a/Newsbot.Collector.Api/Services/IdentityService.cs b/Newsbot.Collector.Api/Services/IdentityService.cs new file mode 100644 index 0000000..2955479 --- /dev/null +++ b/Newsbot.Collector.Api/Services/IdentityService.cs @@ -0,0 +1,114 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; +using Newsbot.Collector.Api.Domain.Results; +using Newsbot.Collector.Domain.Models.Config; + +namespace Newsbot.Collector.Api.Services; + +public interface IIdentityService +{ + AuthenticationResult Register(string email, string password); + AuthenticationResult Login(string email, string password); +} + +public class IdentityService : IIdentityService +{ + private readonly UserManager _userManager; + private readonly JwtSettings _jwtSettings; + + public IdentityService(UserManager userManager, JwtSettings jwtSettings) + { + _userManager = userManager; + _jwtSettings = jwtSettings; + } + + public AuthenticationResult Register(string email, string password) + { + var userExists = _userManager.FindByEmailAsync(email); + userExists.Wait(); + + if (userExists.Result != null) + { + return new AuthenticationResult + { + ErrorMessage = new[] { "A user with this email address already exists" } + }; + } + + var newUser = new IdentityUser + { + UserName = email, + Email = email + }; + + var createdUser = _userManager.CreateAsync(newUser, password); + createdUser.Wait(); + + if (!createdUser.Result.Succeeded) + { + return new AuthenticationResult + { + ErrorMessage = new[] { createdUser.Result.Errors.Select(x => x.Description).ToString() } + }; + } + + return GenerateJwtToken(newUser); + } + + public AuthenticationResult Login(string email, string password) + { + var user =_userManager.FindByEmailAsync(email); + user.Wait(); + + if (user.Result == null) + { + return new AuthenticationResult + { + ErrorMessage = new[] { "User does not exist" } + }; + } + + var hasValidPassword = _userManager.CheckPasswordAsync(user.Result ?? new IdentityUser(), password); + hasValidPassword.Wait(); + + if (!hasValidPassword.Result) + { + return new AuthenticationResult() + { + ErrorMessage = new[] { "Password is invalid" } + }; + } + + return GenerateJwtToken(user.Result ?? new IdentityUser()); + } + + private AuthenticationResult GenerateJwtToken(IdentityUser user) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret ?? ""); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new[] + { + new Claim(JwtRegisteredClaimNames.Sub, user.Email ?? ""), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(JwtRegisteredClaimNames.Email, user.Email ?? ""), + new Claim("id", user.Id) + }), + Expires = DateTime.UtcNow.AddHours(3), + SigningCredentials = + new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + + var token = tokenHandler.CreateToken(tokenDescriptor); + + return new AuthenticationResult + { + IsSuccessful = true, + Token = tokenHandler.WriteToken(token) + }; + } +} \ No newline at end of file From 9e2b44df7c9bcdb3e0a9f23d6f4f04318f0020e6 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:26:19 -0700 Subject: [PATCH 09/16] Controllers have been updated with Authorize calls and tables getting loaded from DI --- .../Controllers/ArticlesController.cs | 12 +++++++----- .../Controllers/CodeProjectController.cs | 3 +++ .../Controllers/DiscordWebHooksController.cs | 9 +++++---- .../Controllers/RssController.cs | 4 ++++ .../Controllers/SourcesController.cs | 16 +++++++--------- .../Controllers/SubscriptionsController.cs | 13 ++++++++----- .../Controllers/YoutubeController.cs | 4 ++++ 7 files changed, 38 insertions(+), 23 deletions(-) diff --git a/Newsbot.Collector.Api/Controllers/ArticlesController.cs b/Newsbot.Collector.Api/Controllers/ArticlesController.cs index 502d04a..e4626ba 100644 --- a/Newsbot.Collector.Api/Controllers/ArticlesController.cs +++ b/Newsbot.Collector.Api/Controllers/ArticlesController.cs @@ -1,6 +1,7 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using Newsbot.Collector.Database.Repositories; using Newsbot.Collector.Domain.Dto; using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Models; @@ -9,17 +10,18 @@ namespace Newsbot.Collector.Api.Controllers; [ApiController] [Route("api/articles")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class ArticlesController : ControllerBase { - private readonly IArticlesRepository _articles; private readonly ILogger _logger; + private readonly IArticlesRepository _articles; private readonly ISourcesRepository _sources; - public ArticlesController(ILogger logger, IOptions settings) + public ArticlesController(ILogger logger, IArticlesRepository articles, ISourcesRepository sources) { _logger = logger; - _articles = new ArticlesTable(settings.Value.Database); - _sources = new SourcesTable(settings.Value.Database); + _articles = articles; + _sources = sources; } [HttpGet(Name = "GetArticles")] diff --git a/Newsbot.Collector.Api/Controllers/CodeProjectController.cs b/Newsbot.Collector.Api/Controllers/CodeProjectController.cs index 491d5ef..11033db 100644 --- a/Newsbot.Collector.Api/Controllers/CodeProjectController.cs +++ b/Newsbot.Collector.Api/Controllers/CodeProjectController.cs @@ -1,4 +1,6 @@ using Hangfire; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newsbot.Collector.Domain.Models.Config; @@ -8,6 +10,7 @@ namespace Newsbot.Collector.Api.Controllers; [ApiController] [Route("api/codeprojects")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class CodeProjectController { private readonly ConfigSectionConnectionStrings _connectionStrings; diff --git a/Newsbot.Collector.Api/Controllers/DiscordWebHooksController.cs b/Newsbot.Collector.Api/Controllers/DiscordWebHooksController.cs index 194c6ff..b33806d 100644 --- a/Newsbot.Collector.Api/Controllers/DiscordWebHooksController.cs +++ b/Newsbot.Collector.Api/Controllers/DiscordWebHooksController.cs @@ -1,4 +1,6 @@ using System.Net; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newsbot.Collector.Database.Repositories; @@ -11,17 +13,16 @@ namespace Newsbot.Collector.Api.Controllers; [ApiController] [Route("api/discord/webhooks")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class DiscordWebHookController : ControllerBase { private readonly ILogger _logger; - private readonly ConnectionStrings _settings; private readonly IDiscordWebHooksRepository _webhooks; - public DiscordWebHookController(ILogger logger, IOptions settings) + public DiscordWebHookController(ILogger logger, IOptions settings, IDiscordWebHooksRepository webhooks) { _logger = logger; - _settings = settings.Value; - _webhooks = new DiscordWebhooksTable(_settings.Database); + _webhooks = webhooks; } [HttpGet(Name = "GetDiscordWebhooks")] diff --git a/Newsbot.Collector.Api/Controllers/RssController.cs b/Newsbot.Collector.Api/Controllers/RssController.cs index 68ab76d..c97acad 100644 --- a/Newsbot.Collector.Api/Controllers/RssController.cs +++ b/Newsbot.Collector.Api/Controllers/RssController.cs @@ -1,13 +1,17 @@ using Hangfire; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newsbot.Collector.Domain.Models.Config; +using Newsbot.Collector.Domain.Models.Config.Sources; using Newsbot.Collector.Services.Jobs; namespace Newsbot.Collector.Api.Controllers; [ApiController] [Route("api/rss")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class RssController { private readonly ConfigSectionConnectionStrings _connectionStrings; diff --git a/Newsbot.Collector.Api/Controllers/SourcesController.cs b/Newsbot.Collector.Api/Controllers/SourcesController.cs index 41bd859..e6cd0f2 100644 --- a/Newsbot.Collector.Api/Controllers/SourcesController.cs +++ b/Newsbot.Collector.Api/Controllers/SourcesController.cs @@ -1,3 +1,5 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newsbot.Collector.Database.Repositories; @@ -12,22 +14,18 @@ namespace Newsbot.Collector.Api.Controllers; [ApiController] [Route("api/sources")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class SourcesController : ControllerBase { - private readonly IArticlesRepository _articles; - - //private readonly ConnectionStrings _settings; - private readonly IIconsRepository _icons; private readonly ILogger _logger; + private readonly IIconsRepository _icons; private readonly ISourcesRepository _sources; - public SourcesController(ILogger logger, IOptions settings) + public SourcesController(ILogger logger, IIconsRepository icons, ISourcesRepository sources) { _logger = logger; - //_settings = settings.Value; - _sources = new SourcesTable(settings.Value.Database); - _icons = new IconsTable(settings.Value.Database); - _articles = new ArticlesTable(settings.Value.Database); + _icons = icons; + _sources = sources; } [HttpGet(Name = "GetSources")] diff --git a/Newsbot.Collector.Api/Controllers/SubscriptionsController.cs b/Newsbot.Collector.Api/Controllers/SubscriptionsController.cs index 981a6b2..e8dafdd 100644 --- a/Newsbot.Collector.Api/Controllers/SubscriptionsController.cs +++ b/Newsbot.Collector.Api/Controllers/SubscriptionsController.cs @@ -1,3 +1,5 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newsbot.Collector.Database.Repositories; @@ -10,19 +12,20 @@ namespace Newsbot.Collector.Api.Controllers; [ApiController] [Route("api/subscriptions")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class SubscriptionsController : ControllerBase { - private readonly IDiscordWebHooksRepository _discord; private readonly ILogger _logger; + private readonly IDiscordWebHooksRepository _discord; private readonly ISourcesRepository _sources; private readonly ISubscriptionRepository _subscription; - public SubscriptionsController(ILogger logger, IOptions settings) + public SubscriptionsController(ILogger logger, IDiscordWebHooksRepository discord, ISourcesRepository sources, ISubscriptionRepository subscription) { _logger = logger; - _subscription = new SubscriptionsTable(settings.Value.Database); - _discord = new DiscordWebhooksTable(settings.Value.Database); - _sources = new SourcesTable(settings.Value.Database); + _discord = discord; + _sources = sources; + _subscription = subscription; } [HttpGet(Name = "ListSubscriptions")] diff --git a/Newsbot.Collector.Api/Controllers/YoutubeController.cs b/Newsbot.Collector.Api/Controllers/YoutubeController.cs index b569481..6767c79 100644 --- a/Newsbot.Collector.Api/Controllers/YoutubeController.cs +++ b/Newsbot.Collector.Api/Controllers/YoutubeController.cs @@ -1,7 +1,10 @@ using Hangfire; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newsbot.Collector.Domain.Models.Config; +using Newsbot.Collector.Domain.Models.Config.Sources; using Newsbot.Collector.Services.Jobs; using ILogger = Grpc.Core.Logging.ILogger; @@ -9,6 +12,7 @@ namespace Newsbot.Collector.Api.Controllers; [ApiController] [Route("api/youtube")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class YoutubeController { private readonly ILogger _logger; From 7b1407c2cbb3c4587d43e1b36aa709f09523ba5b Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Thu, 6 Jul 2023 22:26:49 -0700 Subject: [PATCH 10/16] JwtSettings has been added to config --- Newsbot.Collector.Domain/Models/Config/JwtSettings.cs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Newsbot.Collector.Domain/Models/Config/JwtSettings.cs diff --git a/Newsbot.Collector.Domain/Models/Config/JwtSettings.cs b/Newsbot.Collector.Domain/Models/Config/JwtSettings.cs new file mode 100644 index 0000000..ace1738 --- /dev/null +++ b/Newsbot.Collector.Domain/Models/Config/JwtSettings.cs @@ -0,0 +1,6 @@ +namespace Newsbot.Collector.Domain.Models.Config; + +public class JwtSettings +{ + public string? Secret { get; set; } +} \ No newline at end of file From 58229b134865915322f0336d25158a72502d3780 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 9 Jul 2023 21:29:33 -0700 Subject: [PATCH 11/16] Moving the Subscriptions over to DiscordNotifications as this will be the automated posting compared to user feeds --- .../DiscordNotificationController.cs | 130 +++++++++++++++++ .../Controllers/SubscriptionsController.cs | 133 ------------------ Newsbot.Collector.Api/Program.cs | 2 +- Newsbot.Collector.Database/DatabaseContext.cs | 3 +- ...nsTable.cs => DiscordNotificationTable.cs} | 38 ++--- ...to.cs => DiscordNotificationDetailsDto.cs} | 12 +- ...iptionDto.cs => DiscordNotificationDto.cs} | 6 +- ...Entity.cs => DiscordNotificationEntity.cs} | 2 +- .../IDiscordNotificationRepository.cs | 18 +++ .../Interfaces/ISubscriptionsRepository.cs | 18 --- .../Jobs/DiscordNotificationJob.cs | 17 ++- .../Jobs/DiscordNotificationJobTest.cs | 4 +- .../Tables/DiscordQueueTableTests.cs | 16 +++ 13 files changed, 208 insertions(+), 191 deletions(-) create mode 100644 Newsbot.Collector.Api/Controllers/DiscordNotificationController.cs delete mode 100644 Newsbot.Collector.Api/Controllers/SubscriptionsController.cs rename Newsbot.Collector.Database/Repositories/{SubscriptionsTable.cs => DiscordNotificationTable.cs} (56%) rename Newsbot.Collector.Domain/Dto/{SubscriptionDetailsDto.cs => DiscordNotificationDetailsDto.cs} (57%) rename Newsbot.Collector.Domain/Dto/{SubscriptionDto.cs => DiscordNotificationDto.cs} (79%) rename Newsbot.Collector.Domain/Entities/{SubscriptionEntity.cs => DiscordNotificationEntity.cs} (87%) create mode 100644 Newsbot.Collector.Domain/Interfaces/IDiscordNotificationRepository.cs delete mode 100644 Newsbot.Collector.Domain/Interfaces/ISubscriptionsRepository.cs create mode 100644 Newsbot.Collector.Tests/Tables/DiscordQueueTableTests.cs diff --git a/Newsbot.Collector.Api/Controllers/DiscordNotificationController.cs b/Newsbot.Collector.Api/Controllers/DiscordNotificationController.cs new file mode 100644 index 0000000..c2c8e55 --- /dev/null +++ b/Newsbot.Collector.Api/Controllers/DiscordNotificationController.cs @@ -0,0 +1,130 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Newsbot.Collector.Domain.Dto; +using Newsbot.Collector.Domain.Entities; +using Newsbot.Collector.Domain.Interfaces; + +namespace Newsbot.Collector.Api.Controllers; + +[ApiController] +[Route("api/subscriptions")] +[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] +public class DiscordNotificationController : ControllerBase +{ + private readonly ILogger _logger; + private readonly IDiscordWebHooksRepository _discord; + private readonly ISourcesRepository _sources; + private readonly IDiscordNotificationRepository _discordNotification; + + public DiscordNotificationController(ILogger logger, IDiscordWebHooksRepository discord, ISourcesRepository sources, IDiscordNotificationRepository discordNotification) + { + _logger = logger; + _discord = discord; + _sources = sources; + _discordNotification = discordNotification; + } + + [HttpGet(Name = "ListSubscriptions")] + public IEnumerable List(int page) + { + var res = new List(); + var items = _discordNotification.List(page); + foreach (var item in items) res.Add(DiscordNotificationDto.Convert(item)); + return res; + } + + [HttpGet("{id}")] + public DiscordNotificationDto GetById(Guid id) + { + return DiscordNotificationDto.Convert(_discordNotification.GetById(id)); + } + + [HttpGet("{id}/details")] + public DiscordNotificationDetailsDto GetDetailsById(Guid id) + { + var sub = _discordNotification.GetById(id); + var webhook = _discord.GetById(sub.DiscordWebHookId); + var source = _sources.GetById(sub.SourceId); + + return DiscordNotificationDetailsDto.Convert(sub, source, webhook); + } + + [HttpPost("{id}/delete")] + public void DeleteById(Guid id) + { + _discordNotification.Delete(id); + } + + [HttpGet("by/discordId/{id}")] + public IEnumerable GetByDiscordId(Guid id) + { + var res = new List(); + var items = _discordNotification.ListByWebhook(id); + foreach (var item in items) res.Add(DiscordNotificationDto.Convert(item)); + return res; + } + + [HttpGet("by/sourceId/{id}")] + public IEnumerable GetBySourceId(Guid id) + { + var res = new List(); + var items = _discordNotification.ListBySourceId(id); + foreach (var item in items) res.Add(DiscordNotificationDto.Convert(item)); + return res; + } + + [HttpPost(Name = "New Subscription")] + public ActionResult New(Guid sourceId, Guid discordId) + { + if (sourceId == Guid.Empty) return new BadRequestResult(); + if (discordId == Guid.Empty) return new BadRequestResult(); + + var exists = _discordNotification.GetByWebhookAndSource(discordId, sourceId); + if (exists.Id != Guid.Empty) return DiscordNotificationDto.Convert(exists); + + var discord = _discord.GetById(discordId); + if (discord.Id == Guid.Empty) return new BadRequestResult(); + + var source = _sources.GetById(sourceId); + if (source.Id == Guid.Empty) return new BadRequestResult(); + + var item = _discordNotification.New(new DiscordNotificationEntity + { + Id = Guid.NewGuid(), + SourceId = sourceId, + DiscordWebHookId = discordId, + CodeAllowCommits = false, + CodeAllowReleases = false + }); + + return DiscordNotificationDto.Convert(item); + } + + [HttpPost("new/codeproject")] + public ActionResult NewCodeProjectSubscription(Guid sourceId, Guid discordId, bool allowReleases, + bool allowCommits) + { + if (sourceId == Guid.Empty) return new BadRequestResult(); + if (discordId == Guid.Empty) return new BadRequestResult(); + + var exists = _discordNotification.GetByWebhookAndSource(discordId, sourceId); + if (exists.Id != Guid.Empty) return DiscordNotificationDto.Convert(exists); + + var discord = _discord.GetById(discordId); + if (discord.Id == Guid.Empty) return new BadRequestResult(); + + var source = _sources.GetById(sourceId); + if (source.Id == Guid.Empty) return new BadRequestResult(); + + var sub = _discordNotification.New(new DiscordNotificationEntity + { + DiscordWebHookId = discordId, + SourceId = sourceId, + CodeAllowCommits = allowCommits, + CodeAllowReleases = allowReleases + }); + + return new DiscordNotificationDto(); + } +} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Controllers/SubscriptionsController.cs b/Newsbot.Collector.Api/Controllers/SubscriptionsController.cs deleted file mode 100644 index e8dafdd..0000000 --- a/Newsbot.Collector.Api/Controllers/SubscriptionsController.cs +++ /dev/null @@ -1,133 +0,0 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Newsbot.Collector.Database.Repositories; -using Newsbot.Collector.Domain.Dto; -using Newsbot.Collector.Domain.Entities; -using Newsbot.Collector.Domain.Interfaces; -using Newsbot.Collector.Domain.Models; - -namespace Newsbot.Collector.Api.Controllers; - -[ApiController] -[Route("api/subscriptions")] -[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public class SubscriptionsController : ControllerBase -{ - private readonly ILogger _logger; - private readonly IDiscordWebHooksRepository _discord; - private readonly ISourcesRepository _sources; - private readonly ISubscriptionRepository _subscription; - - public SubscriptionsController(ILogger logger, IDiscordWebHooksRepository discord, ISourcesRepository sources, ISubscriptionRepository subscription) - { - _logger = logger; - _discord = discord; - _sources = sources; - _subscription = subscription; - } - - [HttpGet(Name = "ListSubscriptions")] - public IEnumerable List(int page) - { - var res = new List(); - var items = _subscription.List(page); - foreach (var item in items) res.Add(SubscriptionDto.Convert(item)); - return res; - } - - [HttpGet("{id}")] - public SubscriptionDto GetById(Guid id) - { - return SubscriptionDto.Convert(_subscription.GetById(id)); - } - - [HttpGet("{id}/details")] - public SubscriptionDetailsDto GetDetailsById(Guid id) - { - var sub = _subscription.GetById(id); - var webhook = _discord.GetById(sub.DiscordWebHookId); - var source = _sources.GetById(sub.SourceId); - - return SubscriptionDetailsDto.Convert(sub, source, webhook); - } - - [HttpPost("{id}/delete")] - public void DeleteById(Guid id) - { - _subscription.Delete(id); - } - - [HttpGet("by/discordid/{id}")] - public IEnumerable GetByDiscordId(Guid id) - { - var res = new List(); - var items = _subscription.ListByWebhook(id); - foreach (var item in items) res.Add(SubscriptionDto.Convert(item)); - return res; - } - - [HttpGet("by/sourceId/{id}")] - public IEnumerable GetBySourceId(Guid id) - { - var res = new List(); - var items = _subscription.ListBySourceId(id); - foreach (var item in items) res.Add(SubscriptionDto.Convert(item)); - return res; - } - - [HttpPost(Name = "New Subscription")] - public ActionResult New(Guid sourceId, Guid discordId) - { - if (sourceId == Guid.Empty) return new BadRequestResult(); - if (discordId == Guid.Empty) return new BadRequestResult(); - - var exists = _subscription.GetByWebhookAndSource(discordId, sourceId); - if (exists.Id != Guid.Empty) return SubscriptionDto.Convert(exists); - - var discord = _discord.GetById(discordId); - if (discord.Id == Guid.Empty) return new BadRequestResult(); - - var source = _sources.GetById(sourceId); - if (source.Id == Guid.Empty) return new BadRequestResult(); - - var item = _subscription.New(new SubscriptionEntity - { - Id = Guid.NewGuid(), - SourceId = sourceId, - DiscordWebHookId = discordId, - CodeAllowCommits = false, - CodeAllowReleases = false - }); - - return SubscriptionDto.Convert(item); - } - - [HttpPost("new/codeproject")] - public ActionResult NewCodeProjectSubscription(Guid sourceId, Guid discordId, bool allowReleases, - bool allowCommits) - { - if (sourceId == Guid.Empty) return new BadRequestResult(); - if (discordId == Guid.Empty) return new BadRequestResult(); - - var exists = _subscription.GetByWebhookAndSource(discordId, sourceId); - if (exists.Id != Guid.Empty) return SubscriptionDto.Convert(exists); - - var discord = _discord.GetById(discordId); - if (discord.Id == Guid.Empty) return new BadRequestResult(); - - var source = _sources.GetById(sourceId); - if (source.Id == Guid.Empty) return new BadRequestResult(); - - var sub = _subscription.New(new SubscriptionEntity - { - DiscordWebHookId = discordId, - SourceId = sourceId, - CodeAllowCommits = allowCommits, - CodeAllowReleases = allowReleases - }); - - return new SubscriptionDto(); - } -} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Program.cs b/Newsbot.Collector.Api/Program.cs index ee12181..b6d9ec4 100644 --- a/Newsbot.Collector.Api/Program.cs +++ b/Newsbot.Collector.Api/Program.cs @@ -48,7 +48,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); // Configure Identity builder.Services.AddScoped(); diff --git a/Newsbot.Collector.Database/DatabaseContext.cs b/Newsbot.Collector.Database/DatabaseContext.cs index c4d8710..6cb50cc 100644 --- a/Newsbot.Collector.Database/DatabaseContext.cs +++ b/Newsbot.Collector.Database/DatabaseContext.cs @@ -10,11 +10,12 @@ namespace Newsbot.Collector.Database; public class DatabaseContext : IdentityDbContext { public DbSet Articles { get; set; } = null!; + + public DbSet DiscordNotification { get; set; } = null!; public DbSet DiscordQueue { get; set; } = null!; public DbSet DiscordWebhooks { get; set; } = null!; public DbSet Icons { get; set; } = null!; public DbSet Sources { get; set; } = null!; - public DbSet Subscriptions { get; set; } = null!; //public DbSet Users { get; set; } = null!; diff --git a/Newsbot.Collector.Database/Repositories/SubscriptionsTable.cs b/Newsbot.Collector.Database/Repositories/DiscordNotificationTable.cs similarity index 56% rename from Newsbot.Collector.Database/Repositories/SubscriptionsTable.cs rename to Newsbot.Collector.Database/Repositories/DiscordNotificationTable.cs index 871a6d5..9132d8b 100644 --- a/Newsbot.Collector.Database/Repositories/SubscriptionsTable.cs +++ b/Newsbot.Collector.Database/Repositories/DiscordNotificationTable.cs @@ -8,78 +8,78 @@ using Npgsql; namespace Newsbot.Collector.Database.Repositories; -public class SubscriptionsTable : ISubscriptionRepository +public class DiscordNotificationTable : IDiscordNotificationRepository { //private readonly string _connectionString; private DatabaseContext _context; - public SubscriptionsTable(string connectionString) + public DiscordNotificationTable(string connectionString) { //_connectionString = connectionString; _context = new DatabaseContext(connectionString); } - public SubscriptionsTable(DatabaseContext context) + public DiscordNotificationTable(DatabaseContext context) { _context = context; } - public SubscriptionEntity New(SubscriptionEntity model) + public DiscordNotificationEntity New(DiscordNotificationEntity model) { model.Id = new Guid(); //using var context = new DatabaseContext(_connectionString); - _context.Subscriptions.Add(model); + _context.DiscordNotification.Add(model); _context.SaveChanges(); return model; } - public List List(int page = 0, int count = 25) + public List List(int page = 0, int count = 25) { //using var context = new DatabaseContext(_connectionString); - return _context.Subscriptions.Skip(page * count).Take(count).ToList(); + return _context.DiscordNotification.Skip(page * count).Take(count).ToList(); } - public List ListBySourceId(Guid id, int page = 0, int count = 25) + public List ListBySourceId(Guid id, int page = 0, int count = 25) { //using var context = new DatabaseContext(_connectionString); - return _context.Subscriptions.Where(f => f.SourceId.Equals(id)) + return _context.DiscordNotification.Where(f => f.SourceId.Equals(id)) .Skip(page * count) .ToList(); } - public List ListByWebhook(Guid id, int page = 0, int count = 25) + public List ListByWebhook(Guid id, int page = 0, int count = 25) { //using var context = new DatabaseContext(_connectionString); - return _context.Subscriptions.Where(f => f.DiscordWebHookId.Equals(id)).Skip(page * count).ToList(); + return _context.DiscordNotification.Where(f => f.DiscordWebHookId.Equals(id)).Skip(page * count).ToList(); } - public SubscriptionEntity GetById(Guid id) + public DiscordNotificationEntity GetById(Guid id) { //using var context = new DatabaseContext(_connectionString); - var res = _context.Subscriptions + var res = _context.DiscordNotification .FirstOrDefault(f => f.Id.Equals(id)); - return res ??= new SubscriptionEntity(); + return res ??= new DiscordNotificationEntity(); } - public SubscriptionEntity GetByWebhookAndSource(Guid webhookId, Guid sourceId) + public DiscordNotificationEntity GetByWebhookAndSource(Guid webhookId, Guid sourceId) { //using var context = new DatabaseContext(_connectionString); - var res = _context.Subscriptions + var res = _context.DiscordNotification .Where(f => f.DiscordWebHookId.Equals(webhookId)) .FirstOrDefault(f => f.SourceId.Equals(sourceId)); - return res ??= new SubscriptionEntity(); + return res ??= new DiscordNotificationEntity(); } public void Delete(Guid id) { //using var context = new DatabaseContext(_connectionString); - var res = _context.Subscriptions.FirstOrDefault(f => f.Id.Equals(id)); + var res = _context.DiscordNotification.FirstOrDefault(f => f.Id.Equals(id)); if (res is null) { return; } - _context.Subscriptions.Remove(res); + _context.DiscordNotification.Remove(res); _context.SaveChanges(); } diff --git a/Newsbot.Collector.Domain/Dto/SubscriptionDetailsDto.cs b/Newsbot.Collector.Domain/Dto/DiscordNotificationDetailsDto.cs similarity index 57% rename from Newsbot.Collector.Domain/Dto/SubscriptionDetailsDto.cs rename to Newsbot.Collector.Domain/Dto/DiscordNotificationDetailsDto.cs index 90ae0af..1eaab30 100644 --- a/Newsbot.Collector.Domain/Dto/SubscriptionDetailsDto.cs +++ b/Newsbot.Collector.Domain/Dto/DiscordNotificationDetailsDto.cs @@ -3,7 +3,7 @@ using Newsbot.Collector.Domain.Models; namespace Newsbot.Collector.Domain.Dto; -public class SubscriptionDetailsDto +public class DiscordNotificationDetailsDto { public Guid Id { get; set; } public bool CodeAllowReleases { get; set; } @@ -11,14 +11,14 @@ public class SubscriptionDetailsDto public SourceDto? Source { get; set; } public DiscordWebHookDto? DiscordWebHook { get; set; } - public static SubscriptionDetailsDto Convert(SubscriptionEntity subscription, SourceEntity source, + public static DiscordNotificationDetailsDto Convert(DiscordNotificationEntity discordNotification, SourceEntity source, DiscordWebhookEntity discord) { - return new SubscriptionDetailsDto + return new DiscordNotificationDetailsDto { - Id = subscription.Id, - CodeAllowCommits = subscription.CodeAllowCommits, - CodeAllowReleases = subscription.CodeAllowReleases, + Id = discordNotification.Id, + CodeAllowCommits = discordNotification.CodeAllowCommits, + CodeAllowReleases = discordNotification.CodeAllowReleases, Source = SourceDto.Convert(source), DiscordWebHook = DiscordWebHookDto.Convert(discord) }; diff --git a/Newsbot.Collector.Domain/Dto/SubscriptionDto.cs b/Newsbot.Collector.Domain/Dto/DiscordNotificationDto.cs similarity index 79% rename from Newsbot.Collector.Domain/Dto/SubscriptionDto.cs rename to Newsbot.Collector.Domain/Dto/DiscordNotificationDto.cs index 56b7efb..634ac39 100644 --- a/Newsbot.Collector.Domain/Dto/SubscriptionDto.cs +++ b/Newsbot.Collector.Domain/Dto/DiscordNotificationDto.cs @@ -3,7 +3,7 @@ using Newsbot.Collector.Domain.Models; namespace Newsbot.Collector.Domain.Dto; -public class SubscriptionDto +public class DiscordNotificationDto { public Guid Id { get; set; } public Guid SourceId { get; set; } @@ -11,9 +11,9 @@ public class SubscriptionDto public bool CodeAllowReleases { get; set; } public bool CodeAllowCommits { get; set; } - public static SubscriptionDto Convert(SubscriptionEntity model) + public static DiscordNotificationDto Convert(DiscordNotificationEntity model) { - return new SubscriptionDto + return new DiscordNotificationDto { Id = model.Id, SourceId = model.SourceId, diff --git a/Newsbot.Collector.Domain/Entities/SubscriptionEntity.cs b/Newsbot.Collector.Domain/Entities/DiscordNotificationEntity.cs similarity index 87% rename from Newsbot.Collector.Domain/Entities/SubscriptionEntity.cs rename to Newsbot.Collector.Domain/Entities/DiscordNotificationEntity.cs index ae405ed..efe4105 100644 --- a/Newsbot.Collector.Domain/Entities/SubscriptionEntity.cs +++ b/Newsbot.Collector.Domain/Entities/DiscordNotificationEntity.cs @@ -1,6 +1,6 @@ namespace Newsbot.Collector.Domain.Entities; -public class SubscriptionEntity +public class DiscordNotificationEntity { public Guid Id { get; set; } public bool CodeAllowReleases { get; set; } diff --git a/Newsbot.Collector.Domain/Interfaces/IDiscordNotificationRepository.cs b/Newsbot.Collector.Domain/Interfaces/IDiscordNotificationRepository.cs new file mode 100644 index 0000000..c71e137 --- /dev/null +++ b/Newsbot.Collector.Domain/Interfaces/IDiscordNotificationRepository.cs @@ -0,0 +1,18 @@ +using Newsbot.Collector.Domain.Entities; +using Newsbot.Collector.Domain.Models; + +namespace Newsbot.Collector.Domain.Interfaces; + +public interface IDiscordNotificationRepository +{ + DiscordNotificationEntity New(DiscordNotificationEntity model); + + List List(int page = 0, int count = 25); + List ListBySourceId(Guid id, int page = 0, int count = 25); + List ListByWebhook(Guid id, int page = 0, int count = 25); + + DiscordNotificationEntity GetById(Guid id); + DiscordNotificationEntity GetByWebhookAndSource(Guid webhookId, Guid sourceId); + + void Delete(Guid id); +} \ No newline at end of file diff --git a/Newsbot.Collector.Domain/Interfaces/ISubscriptionsRepository.cs b/Newsbot.Collector.Domain/Interfaces/ISubscriptionsRepository.cs deleted file mode 100644 index c955bbf..0000000 --- a/Newsbot.Collector.Domain/Interfaces/ISubscriptionsRepository.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Newsbot.Collector.Domain.Entities; -using Newsbot.Collector.Domain.Models; - -namespace Newsbot.Collector.Domain.Interfaces; - -public interface ISubscriptionRepository -{ - SubscriptionEntity New(SubscriptionEntity model); - - List List(int page = 0, int count = 25); - List ListBySourceId(Guid id, int page = 0, int count = 25); - List ListByWebhook(Guid id, int page = 0, int count = 25); - - SubscriptionEntity GetById(Guid id); - SubscriptionEntity GetByWebhookAndSource(Guid webhookId, Guid sourceId); - - void Delete(Guid id); -} \ No newline at end of file diff --git a/Newsbot.Collector.Services/Jobs/DiscordNotificationJob.cs b/Newsbot.Collector.Services/Jobs/DiscordNotificationJob.cs index 6ae432e..9544c63 100644 --- a/Newsbot.Collector.Services/Jobs/DiscordNotificationJob.cs +++ b/Newsbot.Collector.Services/Jobs/DiscordNotificationJob.cs @@ -1,3 +1,4 @@ +using Newsbot.Collector.Database; using Newsbot.Collector.Database.Repositories; using Newsbot.Collector.Domain.Entities; using Newsbot.Collector.Domain.Interfaces; @@ -32,13 +33,13 @@ public class DiscordNotificationJobOptions public class DiscordNotificationJob { private const string JobName = "DiscordNotifications"; + private ILogger _logger; + //private DatabaseContext _databaseContext; private IArticlesRepository _article; private IIconsRepository _icons; - private ILogger _logger; - private IDiscordQueueRepository _queue; private ISourcesRepository _sources; - private ISubscriptionRepository _subs; + private IDiscordNotificationRepository _subs; private IDiscordWebHooksRepository _webhook; public DiscordNotificationJob() @@ -47,18 +48,19 @@ public class DiscordNotificationJob _article = new ArticlesTable(""); _webhook = new DiscordWebhooksTable(""); _sources = new SourcesTable(""); - _subs = new SubscriptionsTable(""); + _subs = new DiscordNotificationTable(""); _icons = new IconsTable(""); _logger = JobLogger.GetLogger("", JobName); } public void InitAndExecute(DiscordNotificationJobOptions options) { + //_databaseContext = new DatabaseContext(options.ConnectionString ?? ""); _queue = new DiscordQueueTable(options.ConnectionString ?? ""); _article = new ArticlesTable(options.ConnectionString ?? ""); _webhook = new DiscordWebhooksTable(options.ConnectionString ?? ""); _sources = new SourcesTable(options.ConnectionString ?? ""); - _subs = new SubscriptionsTable(options.ConnectionString ?? ""); + _subs = new DiscordNotificationTable(options.ConnectionString ?? ""); _icons = new IconsTable(options.ConnectionString ?? ""); _logger = JobLogger.GetLogger(options.OpenTelemetry ?? "", JobName); @@ -76,6 +78,7 @@ public class DiscordNotificationJob private void Execute() { //collect all the new requests + var requests = _queue.List(100); _logger.Debug($"{JobName} - Collected {requests.Count} items to send"); @@ -121,7 +124,7 @@ public class DiscordNotificationJob _queue.Delete(request.Id); } - public void SendSubscriptionNotification(Guid requestId, ArticlesEntity articleDetails, SourceEntity sourceDetails, IconEntity sourceIcon, SubscriptionEntity sub) + public void SendSubscriptionNotification(Guid requestId, ArticlesEntity articleDetails, SourceEntity sourceDetails, IconEntity sourceIcon, DiscordNotificationEntity sub) { // Check if the subscription code flags // If the article is a code commit and the subscription does not want them, skip. @@ -134,7 +137,7 @@ public class DiscordNotificationJob var discordDetails = _webhook.GetById(sub.DiscordWebHookId); if (discordDetails.Enabled == false) return; - var client = new DiscordWebhookClient(discordDetails.Url); + var client = new DiscordClient(discordDetails.Url); try { client.SendMessage(GenerateDiscordMessage(sourceDetails, articleDetails, sourceIcon)); diff --git a/Newsbot.Collector.Tests/Jobs/DiscordNotificationJobTest.cs b/Newsbot.Collector.Tests/Jobs/DiscordNotificationJobTest.cs index a3b3c71..ed50aae 100644 --- a/Newsbot.Collector.Tests/Jobs/DiscordNotificationJobTest.cs +++ b/Newsbot.Collector.Tests/Jobs/DiscordNotificationJobTest.cs @@ -10,7 +10,7 @@ public class DiscordNotificationJobTest public void PostTestMessage() { var uri = ""; - var webhookClient = new DiscordWebhookClient(uri); + var webhookClient = new DiscordClient(uri); var client = new DiscordNotificationJob(); var msg = client.GenerateDiscordMessage(new SourceEntity @@ -78,7 +78,7 @@ public class DiscordNotificationJobTest Id = Guid.NewGuid(), FileName = "https://www.redditstatic.com/desktop2x/img/favicon/android-icon-192x192.png" }, - new SubscriptionEntity + new DiscordNotificationEntity { CodeAllowCommits = false, CodeAllowReleases = true diff --git a/Newsbot.Collector.Tests/Tables/DiscordQueueTableTests.cs b/Newsbot.Collector.Tests/Tables/DiscordQueueTableTests.cs new file mode 100644 index 0000000..5629d51 --- /dev/null +++ b/Newsbot.Collector.Tests/Tables/DiscordQueueTableTests.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Configuration; +using Newsbot.Collector.Database.Repositories; +using Newsbot.Collector.Domain.Consts; + +namespace Newsbot.Collector.Tests.Tables; + +public class DiscordQueueTableTests +{ + [Fact] + public void TableListsData() + { + var str = TestHelper.LoadConfig().GetConnectionString("Database") ?? ""; + var context = new DiscordQueueTable(str); + var results = context.List(); + } +} \ No newline at end of file From 597470fd6996d2e7ca8bd2c827447b59a8270111 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 9 Jul 2023 21:30:23 -0700 Subject: [PATCH 12/16] Renamed the Discord Notification client to help with naming issues --- .../Interfaces/IDiscordNotificationClient.cs | 2 +- .../Discord/{DiscordWebhookClient.cs => DiscordClient.cs} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename Newsbot.Collector.Services/Notifications/Discord/{DiscordWebhookClient.cs => DiscordClient.cs} (90%) diff --git a/Newsbot.Collector.Domain/Interfaces/IDiscordNotificationClient.cs b/Newsbot.Collector.Domain/Interfaces/IDiscordNotificationClient.cs index 864d6a3..57d99ef 100644 --- a/Newsbot.Collector.Domain/Interfaces/IDiscordNotificationClient.cs +++ b/Newsbot.Collector.Domain/Interfaces/IDiscordNotificationClient.cs @@ -2,7 +2,7 @@ using Newsbot.Collector.Domain.Models; namespace Newsbot.Collector.Domain.Interfaces; -public interface IDiscordNotificatioClient +public interface IDiscordClient { void SendMessage(DiscordMessage payload); } \ No newline at end of file diff --git a/Newsbot.Collector.Services/Notifications/Discord/DiscordWebhookClient.cs b/Newsbot.Collector.Services/Notifications/Discord/DiscordClient.cs similarity index 90% rename from Newsbot.Collector.Services/Notifications/Discord/DiscordWebhookClient.cs rename to Newsbot.Collector.Services/Notifications/Discord/DiscordClient.cs index 8559a71..b8f7564 100644 --- a/Newsbot.Collector.Services/Notifications/Discord/DiscordWebhookClient.cs +++ b/Newsbot.Collector.Services/Notifications/Discord/DiscordClient.cs @@ -6,16 +6,16 @@ using Newtonsoft.Json; namespace Newsbot.Collector.Services.Notifications.Discord; -public class DiscordWebhookClient : IDiscordNotificatioClient +public class DiscordClient : IDiscordClient { private readonly string[] _webhooks; - public DiscordWebhookClient(string webhook) + public DiscordClient(string webhook) { _webhooks = new[] { webhook }; } - public DiscordWebhookClient(string[] webhooks) + public DiscordClient(string[] webhooks) { _webhooks = webhooks; } From d28ad7424f79d79f35273c4021046864ac6cbe64 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 9 Jul 2023 22:10:51 -0700 Subject: [PATCH 13/16] User defined source subscriptions have been added and starting to let users take ownership of discord Notifications --- .../Controllers/v1/UserController.cs | 45 ++ .../Middleware/JwtUserIdExtension.cs | 14 + Newsbot.Collector.Database/DatabaseContext.cs | 2 +- .../20230710050618_SourcesRenamed.Designer.cs | 501 +++++++++++++++++ .../20230710050618_SourcesRenamed.cs | 82 +++ ..._DiscordNotificationOwnerAdded.Designer.cs | 504 ++++++++++++++++++ ...710050913_DiscordNotificationOwnerAdded.cs | 28 + .../DatabaseContextModelSnapshot.cs | 54 +- .../UserSourceSubscriptionTable.cs | 26 + .../Entities/DiscordNotificationEntity.cs | 7 + .../Entities/UserSourceSubscriptionEntity.cs | 20 + .../Interfaces/IUserSourceSubscription.cs | 8 + 12 files changed, 1280 insertions(+), 11 deletions(-) create mode 100644 Newsbot.Collector.Api/Controllers/v1/UserController.cs create mode 100644 Newsbot.Collector.Api/Middleware/JwtUserIdExtension.cs create mode 100644 Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.Designer.cs create mode 100644 Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.cs create mode 100644 Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.Designer.cs create mode 100644 Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.cs create mode 100644 Newsbot.Collector.Database/Repositories/UserSourceSubscriptionTable.cs create mode 100644 Newsbot.Collector.Domain/Entities/UserSourceSubscriptionEntity.cs create mode 100644 Newsbot.Collector.Domain/Interfaces/IUserSourceSubscription.cs diff --git a/Newsbot.Collector.Api/Controllers/v1/UserController.cs b/Newsbot.Collector.Api/Controllers/v1/UserController.cs new file mode 100644 index 0000000..624525c --- /dev/null +++ b/Newsbot.Collector.Api/Controllers/v1/UserController.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using Newsbot.Collector.Api.Authentication; +using Newsbot.Collector.Domain.Entities; +using Newsbot.Collector.Domain.Interfaces; + +namespace Newsbot.Collector.Api.Controllers; + +[ApiController] +[Route("api/v1/user")] +public class UserController : Controller +{ + private ILogger _logger; + private IUserSourceSubscription _subscription; + + public UserController(ILogger logger, IUserSourceSubscription subscription) + { + _logger = logger; + _subscription = subscription; + } + + [HttpPost("listSubscriptions")] + public ActionResult ListSubscriptions() + { + _logger.LogInformation("'/api/v1/user/listSubscriptions' was requested"); + + var userId = HttpContext.GetUserId(); + if (userId.Equals(string.Empty)) + { + _logger.LogWarning("Unable to find the user ID in the JWD Token"); + return new BadRequestResult(); + } + + try + { + var results = _subscription.ListUserSubscriptions(Guid.Parse(userId)); + return new OkObjectResult(results); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to pull subscriptions for userId \'{UserId}\'", userId); + return new NoContentResult(); + } + + } +} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Middleware/JwtUserIdExtension.cs b/Newsbot.Collector.Api/Middleware/JwtUserIdExtension.cs new file mode 100644 index 0000000..80c0e09 --- /dev/null +++ b/Newsbot.Collector.Api/Middleware/JwtUserIdExtension.cs @@ -0,0 +1,14 @@ +namespace Newsbot.Collector.Api.Authentication; + +public static class JwtUserIdExtension +{ + public static string GetUserId(this HttpContext context) + { + if (context.User == null) + { + return string.Empty; + } + + return context.User.Claims.Single(x => x.Type == "id").Value; + } +} \ No newline at end of file diff --git a/Newsbot.Collector.Database/DatabaseContext.cs b/Newsbot.Collector.Database/DatabaseContext.cs index 6cb50cc..b2cdc0d 100644 --- a/Newsbot.Collector.Database/DatabaseContext.cs +++ b/Newsbot.Collector.Database/DatabaseContext.cs @@ -17,7 +17,7 @@ public class DatabaseContext : IdentityDbContext public DbSet Icons { get; set; } = null!; public DbSet Sources { get; set; } = null!; - //public DbSet Users { get; set; } = null!; + public DbSet UserSourceSubscription { get; set; } = null!; private string ConnectionString { get; set; } = ""; diff --git a/Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.Designer.cs b/Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.Designer.cs new file mode 100644 index 0000000..8d06d28 --- /dev/null +++ b/Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.Designer.cs @@ -0,0 +1,501 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Newsbot.Collector.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Newsbot.Collector.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230710050618_SourcesRenamed")] + partial class SourcesRenamed + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorImage") + .HasColumnType("text"); + + b.Property("AuthorName") + .IsRequired() + .HasColumnType("text"); + + b.Property("CodeIsCommit") + .HasColumnType("boolean"); + + b.Property("CodeIsRelease") + .HasColumnType("boolean"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("PubDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Thumbnail") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .HasColumnType("text"); + + b.Property("Video") + .IsRequired() + .HasColumnType("text"); + + b.Property("VideoHeight") + .HasColumnType("integer"); + + b.Property("VideoWidth") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Articles"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CodeAllowCommits") + .HasColumnType("boolean"); + + b.Property("CodeAllowReleases") + .HasColumnType("boolean"); + + b.Property("DiscordWebHookId") + .HasColumnType("uuid"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("DiscordNotification"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArticleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("DiscordQueue"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Server") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DiscordWebhooks"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Icons"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Deleted") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("YoutubeId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Sources"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserSourceSubscription"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.cs b/Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.cs new file mode 100644 index 0000000..51f35ca --- /dev/null +++ b/Newsbot.Collector.Database/Migrations/20230710050618_SourcesRenamed.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Newsbot.Collector.Database.Migrations +{ + /// + public partial class SourcesRenamed : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Subscriptions"); + + migrationBuilder.CreateTable( + name: "DiscordNotification", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CodeAllowReleases = table.Column(type: "boolean", nullable: false), + CodeAllowCommits = table.Column(type: "boolean", nullable: false), + SourceId = table.Column(type: "uuid", nullable: false), + DiscordWebHookId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DiscordNotification", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserSourceSubscription", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + SourceId = table.Column(type: "uuid", nullable: false), + DateAdded = table.Column(type: "timestamp with time zone", nullable: false), + UserId = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserSourceSubscription", x => x.Id); + table.ForeignKey( + name: "FK_UserSourceSubscription_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserSourceSubscription_UserId", + table: "UserSourceSubscription", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DiscordNotification"); + + migrationBuilder.DropTable( + name: "UserSourceSubscription"); + + migrationBuilder.CreateTable( + name: "Subscriptions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + CodeAllowCommits = table.Column(type: "boolean", nullable: false), + CodeAllowReleases = table.Column(type: "boolean", nullable: false), + DiscordWebHookId = table.Column(type: "uuid", nullable: false), + SourceId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Subscriptions", x => x.Id); + }); + } + } +} diff --git a/Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.Designer.cs b/Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.Designer.cs new file mode 100644 index 0000000..9d69fb3 --- /dev/null +++ b/Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.Designer.cs @@ -0,0 +1,504 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Newsbot.Collector.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Newsbot.Collector.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230710050913_DiscordNotificationOwnerAdded")] + partial class DiscordNotificationOwnerAdded + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.ArticlesEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorImage") + .HasColumnType("text"); + + b.Property("AuthorName") + .IsRequired() + .HasColumnType("text"); + + b.Property("CodeIsCommit") + .HasColumnType("boolean"); + + b.Property("CodeIsRelease") + .HasColumnType("boolean"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("PubDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Thumbnail") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .HasColumnType("text"); + + b.Property("Video") + .IsRequired() + .HasColumnType("text"); + + b.Property("VideoHeight") + .HasColumnType("integer"); + + b.Property("VideoWidth") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Articles"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CodeAllowCommits") + .HasColumnType("boolean"); + + b.Property("CodeAllowReleases") + .HasColumnType("boolean"); + + b.Property("DiscordWebHookId") + .HasColumnType("uuid"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DiscordNotification"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArticleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("DiscordQueue"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordWebhookEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Server") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DiscordWebhooks"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.IconEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Icons"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SourceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Deleted") + .HasColumnType("boolean"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Site") + .IsRequired() + .HasColumnType("text"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("YoutubeId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Sources"); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DateAdded") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserSourceSubscription"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.cs b/Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.cs new file mode 100644 index 0000000..7d8b01d --- /dev/null +++ b/Newsbot.Collector.Database/Migrations/20230710050913_DiscordNotificationOwnerAdded.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Newsbot.Collector.Database.Migrations +{ + /// + public partial class DiscordNotificationOwnerAdded : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserId", + table: "DiscordNotification", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UserId", + table: "DiscordNotification"); + } + } +} diff --git a/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs b/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs index 56817b6..61c9b8b 100644 --- a/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Newsbot.Collector.Database/Migrations/DatabaseContextModelSnapshot.cs @@ -277,6 +277,32 @@ namespace Newsbot.Collector.Database.Migrations b.ToTable("Articles"); }); + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordNotificationEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CodeAllowCommits") + .HasColumnType("boolean"); + + b.Property("CodeAllowReleases") + .HasColumnType("boolean"); + + b.Property("DiscordWebHookId") + .HasColumnType("uuid"); + + b.Property("SourceId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DiscordNotification"); + }); + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.DiscordQueueEntity", b => { b.Property("Id") @@ -388,27 +414,26 @@ namespace Newsbot.Collector.Database.Migrations b.ToTable("Sources"); }); - modelBuilder.Entity("Newsbot.Collector.Domain.Entities.SubscriptionEntity", b => + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property("CodeAllowCommits") - .HasColumnType("boolean"); - - b.Property("CodeAllowReleases") - .HasColumnType("boolean"); - - b.Property("DiscordWebHookId") - .HasColumnType("uuid"); + b.Property("DateAdded") + .HasColumnType("timestamp with time zone"); b.Property("SourceId") .HasColumnType("uuid"); + b.Property("UserId") + .HasColumnType("text"); + b.HasKey("Id"); - b.ToTable("Subscriptions"); + b.HasIndex("UserId"); + + b.ToTable("UserSourceSubscription"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -461,6 +486,15 @@ namespace Newsbot.Collector.Database.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("Newsbot.Collector.Domain.Entities.UserSourceSubscriptionEntity", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); #pragma warning restore 612, 618 } } diff --git a/Newsbot.Collector.Database/Repositories/UserSourceSubscriptionTable.cs b/Newsbot.Collector.Database/Repositories/UserSourceSubscriptionTable.cs new file mode 100644 index 0000000..fcfb314 --- /dev/null +++ b/Newsbot.Collector.Database/Repositories/UserSourceSubscriptionTable.cs @@ -0,0 +1,26 @@ +using Newsbot.Collector.Domain.Entities; +using Newsbot.Collector.Domain.Interfaces; + +namespace Newsbot.Collector.Database.Repositories; + +public class UserSourceSubscriptionTable : IUserSourceSubscription +{ + private DatabaseContext _context; + + public UserSourceSubscriptionTable(string connectionString) + { + _context = new DatabaseContext(connectionString); + } + + public UserSourceSubscriptionTable(DatabaseContext context) + { + _context = context; + } + + public List ListUserSubscriptions(Guid userId) + { + var results =_context.UserSourceSubscription.Where(i => i.UserId.Equals(userId)).ToList(); + return results; + } +} + diff --git a/Newsbot.Collector.Domain/Entities/DiscordNotificationEntity.cs b/Newsbot.Collector.Domain/Entities/DiscordNotificationEntity.cs index efe4105..423b577 100644 --- a/Newsbot.Collector.Domain/Entities/DiscordNotificationEntity.cs +++ b/Newsbot.Collector.Domain/Entities/DiscordNotificationEntity.cs @@ -1,3 +1,6 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNetCore.Identity; + namespace Newsbot.Collector.Domain.Entities; public class DiscordNotificationEntity @@ -8,4 +11,8 @@ public class DiscordNotificationEntity public Guid SourceId { get; set; } public Guid DiscordWebHookId { get; set; } + + public string? UserId { get; set; } + //[ForeignKey(nameof(UserId))] + //public IdentityUser User { get; set; } } \ No newline at end of file diff --git a/Newsbot.Collector.Domain/Entities/UserSourceSubscriptionEntity.cs b/Newsbot.Collector.Domain/Entities/UserSourceSubscriptionEntity.cs new file mode 100644 index 0000000..63a279f --- /dev/null +++ b/Newsbot.Collector.Domain/Entities/UserSourceSubscriptionEntity.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNetCore.Identity; + +namespace Newsbot.Collector.Domain.Entities; + +/// +/// This defines the sources a user will want to see as a feed. +/// +public class UserSourceSubscriptionEntity +{ + [Key] + public Guid Id { get; set; } + public Guid SourceId { get; set; } + public DateTimeOffset DateAdded { get; set; } + + public string? UserId { get; set; } + [ForeignKey(nameof(UserId))] + public IdentityUser? User { get; set; } +} \ No newline at end of file diff --git a/Newsbot.Collector.Domain/Interfaces/IUserSourceSubscription.cs b/Newsbot.Collector.Domain/Interfaces/IUserSourceSubscription.cs new file mode 100644 index 0000000..17b30c1 --- /dev/null +++ b/Newsbot.Collector.Domain/Interfaces/IUserSourceSubscription.cs @@ -0,0 +1,8 @@ +using Newsbot.Collector.Domain.Entities; + +namespace Newsbot.Collector.Domain.Interfaces; + +public interface IUserSourceSubscription +{ + List ListUserSubscriptions(Guid userId); +} \ No newline at end of file From fd60906a200e67f82d98eb367518db0a81a42985 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 9 Jul 2023 22:11:30 -0700 Subject: [PATCH 14/16] Adding the auth layers, not 100% finished yet --- .../Controllers/AccountController.cs | 89 +++++++++++++++++++ .../Domain/Response/AuthFailedResponse.cs | 2 +- .../Newsbot.Collector.Api.csproj | 4 - Newsbot.Collector.Api/Program.cs | 1 + Newsbot.Collector.Domain/Dto/UserNewDto.cs | 7 ++ .../Newsbot.Collector.Domain.csproj | 2 + 6 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 Newsbot.Collector.Api/Controllers/AccountController.cs create mode 100644 Newsbot.Collector.Domain/Dto/UserNewDto.cs diff --git a/Newsbot.Collector.Api/Controllers/AccountController.cs b/Newsbot.Collector.Api/Controllers/AccountController.cs new file mode 100644 index 0000000..b11be6a --- /dev/null +++ b/Newsbot.Collector.Api/Controllers/AccountController.cs @@ -0,0 +1,89 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Newsbot.Collector.Api.Domain.Requests; +using Newsbot.Collector.Api.Domain.Response; +using Newsbot.Collector.Api.Services; +using Newsbot.Collector.Domain.Dto; +using Newsbot.Collector.Domain.Entities; + +namespace Newsbot.Collector.Api.Controllers; + +[ApiController] +[Route("/api/account")] +public class AccountController : ControllerBase +{ + private IIdentityService _identityService; + + public AccountController(IIdentityService identityService) + { + _identityService = identityService; + } + + [HttpPost("register")] + public IActionResult Register([FromBody] RegisterUserRequest user) + { + if (!ModelState.IsValid) + { + return new BadRequestObjectResult(new AuthFailedResponse + { + Errors = ModelState.Values + .Select(x => x.Errors + .Select(y => y.ErrorMessage).FirstOrDefault()) + }); + } + + if (user.Email is null) + { + return new BadRequestResult(); + } + + if (user.Password is null) + { + return new BadRequestResult(); + } + + var response = _identityService.Register(user.Email, user.Password); + + if (!response.IsSuccessful) + { + return new BadRequestObjectResult( new AuthFailedResponse + { + Errors = response.ErrorMessage + }); + } + + return new OkObjectResult(new AuthSuccessfulResponse + { + Token = response.Token + }); + } + + [HttpPost("login")] + public IActionResult Login([FromBody] UserLoginRequest request) + { + if (request.Email is null) + { + return new BadRequestResult(); + } + + if (request.Password is null) + { + return new BadRequestResult(); + } + + var response = _identityService.Login(request.Email, request.Password); + + if (!response.IsSuccessful) + { + return new BadRequestObjectResult( new AuthFailedResponse + { + Errors = response.ErrorMessage + }); + } + + return new OkObjectResult(new AuthSuccessfulResponse + { + Token = response.Token + }); + } +} \ No newline at end of file diff --git a/Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs b/Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs index 891c195..79ecf83 100644 --- a/Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs +++ b/Newsbot.Collector.Api/Domain/Response/AuthFailedResponse.cs @@ -2,5 +2,5 @@ namespace Newsbot.Collector.Api.Domain.Response; public class AuthFailedResponse { - public IEnumerable? Errors { get; set; } + public IEnumerable? Errors { get; set; } } \ No newline at end of file diff --git a/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj b/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj index a766358..969a610 100644 --- a/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj +++ b/Newsbot.Collector.Api/Newsbot.Collector.Api.csproj @@ -37,8 +37,4 @@ - - - - diff --git a/Newsbot.Collector.Api/Program.cs b/Newsbot.Collector.Api/Program.cs index b6d9ec4..a0f05c9 100644 --- a/Newsbot.Collector.Api/Program.cs +++ b/Newsbot.Collector.Api/Program.cs @@ -49,6 +49,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Configure Identity builder.Services.AddScoped(); diff --git a/Newsbot.Collector.Domain/Dto/UserNewDto.cs b/Newsbot.Collector.Domain/Dto/UserNewDto.cs new file mode 100644 index 0000000..676429e --- /dev/null +++ b/Newsbot.Collector.Domain/Dto/UserNewDto.cs @@ -0,0 +1,7 @@ +namespace Newsbot.Collector.Domain.Dto; + +public class UserNewDto +{ + public string? Username { get; set; } + public string? Password { get; set; } +} \ No newline at end of file diff --git a/Newsbot.Collector.Domain/Newsbot.Collector.Domain.csproj b/Newsbot.Collector.Domain/Newsbot.Collector.Domain.csproj index 0f6f10f..b355dd0 100644 --- a/Newsbot.Collector.Domain/Newsbot.Collector.Domain.csproj +++ b/Newsbot.Collector.Domain/Newsbot.Collector.Domain.csproj @@ -7,7 +7,9 @@ + + From 008d79cf3dc81747d93388f58431998e4bd24557 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 9 Jul 2023 22:12:13 -0700 Subject: [PATCH 15/16] added try catch for when a site could be down or blocking requests --- .../HtmlParser/HtmlPageReader.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Newsbot.Collector.Services/HtmlParser/HtmlPageReader.cs b/Newsbot.Collector.Services/HtmlParser/HtmlPageReader.cs index 7b36f3d..051b98b 100644 --- a/Newsbot.Collector.Services/HtmlParser/HtmlPageReader.cs +++ b/Newsbot.Collector.Services/HtmlParser/HtmlPageReader.cs @@ -36,11 +36,19 @@ public class HtmlPageReader private string ReadSiteContent(string url) { using var client = new HttpClient(); - var html = client.GetStringAsync(url); - html.Wait(); + try + { + var html = client.GetStringAsync(url); + html.Wait(); - var content = html.Result; - return content; + var content = html.Result; + return content; + } + catch (Exception ex) + { + Console.WriteLine($"Failed to connect to '{url}'. {ex.Message}"); + return ""; + } } public string GetSiteContent() From df348bea5ae9b04e0ba4fe14b6464aa8837a9c11 Mon Sep 17 00:00:00 2001 From: James Tombleson Date: Sun, 9 Jul 2023 22:12:41 -0700 Subject: [PATCH 16/16] Test updated for ef --- Newsbot.Collector.Tests/Tables/ArticlesTableTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Newsbot.Collector.Tests/Tables/ArticlesTableTests.cs b/Newsbot.Collector.Tests/Tables/ArticlesTableTests.cs index e2d247e..392bf37 100644 --- a/Newsbot.Collector.Tests/Tables/ArticlesTableTests.cs +++ b/Newsbot.Collector.Tests/Tables/ArticlesTableTests.cs @@ -23,7 +23,8 @@ public class ArticlesTableTests public void ArticlesListTest() { var cfg = GetConfiguration(); - var client = new ArticlesTable(cfg); + var dbconn = cfg.GetConnectionString("Database"); + var client = new ArticlesTable(dbconn ?? ""); client.List(0, 25); } @@ -33,7 +34,8 @@ public class ArticlesTableTests var uid = Guid.Parse("4ac46772-253c-4c3d-8a2c-29239abd2ad4"); var cfg = GetConfiguration(); - var client = new ArticlesTable(cfg); + var dbconn = cfg.GetConnectionString("Database"); + var client = new ArticlesTable(dbconn ?? ""); var res = client.GetById(uid); if (!res.Id.Equals(uid)) { @@ -45,7 +47,8 @@ public class ArticlesTableTests public void NewRecordTest() { var cfg = GetConfiguration(); - var client = new ArticlesTable(cfg); + var dbconn = cfg.GetConnectionString("Database"); + var client = new ArticlesTable(dbconn ?? ""); var m = new ArticlesEntity { Id = Guid.NewGuid(),