From c53ecd2ada0152cc7e497d851e4eba52d0e4f278 Mon Sep 17 00:00:00 2001 From: Mia Winter Date: Fri, 26 Jan 2024 13:05:48 +0100 Subject: [PATCH] Added Categories --- Wave/Data/ApplicationDbContext.cs | 19 + Wave/Data/Article.cs | 2 +- Wave/Data/ArticleCategory.cs | 7 + Wave/Data/Category.cs | 22 + .../20240126112644_Categories.Designer.cs | 465 ++++++++++++++++++ .../postgres/20240126112644_Categories.cs | 157 ++++++ .../ApplicationDbContextModelSnapshot.cs | 68 ++- Wave/Program.cs | 1 + 8 files changed, 737 insertions(+), 4 deletions(-) create mode 100644 Wave/Data/ArticleCategory.cs create mode 100644 Wave/Data/Category.cs create mode 100644 Wave/Data/Migrations/postgres/20240126112644_Categories.Designer.cs create mode 100644 Wave/Data/Migrations/postgres/20240126112644_Categories.cs diff --git a/Wave/Data/ApplicationDbContext.cs b/Wave/Data/ApplicationDbContext.cs index 369b830..0408d0d 100644 --- a/Wave/Data/ApplicationDbContext.cs +++ b/Wave/Data/ApplicationDbContext.cs @@ -51,6 +51,25 @@ public class ApplicationDbContext(DbContextOptions options .HasConversion(dateTimeOffsetUtcConverter); article.HasQueryFilter(a => !a.IsDeleted); + article.ToTable("Articles"); + }); + + builder.Entity(category => { + category.HasKey(c => c.Id); + category.Property(c => c.Name).IsRequired().HasMaxLength(128); + category.Property(c => c.Color).IsRequired().HasDefaultValue(CategoryColors.Default); + + category.HasMany
().WithMany() + .UsingEntity( + ac => ac.HasOne(a => a.Article).WithMany().OnDelete(DeleteBehavior.NoAction), + ac => ac.HasOne(a => a.Category).WithMany().OnDelete(DeleteBehavior.NoAction), + articleCategory => { + articleCategory.HasKey(ac => ac.Id); + articleCategory.ToTable("ArticleCategories"); + articleCategory.HasQueryFilter(ac => !ac.Article.IsDeleted); + }); + + category.ToTable("Categories"); }); } } \ No newline at end of file diff --git a/Wave/Data/Article.cs b/Wave/Data/Article.cs index ab978f3..79c92e5 100644 --- a/Wave/Data/Article.cs +++ b/Wave/Data/Article.cs @@ -8,7 +8,7 @@ public enum ArticleStatus { Published = 2 } -// TODO:: Add category / tags for MVP +// TODO:: Add tags for MVP ? // TODO:: Archive System (Notice / Redirect to new content?) (Deprecation date?) // TODO:: Reference used files? diff --git a/Wave/Data/ArticleCategory.cs b/Wave/Data/ArticleCategory.cs new file mode 100644 index 0000000..1b150ae --- /dev/null +++ b/Wave/Data/ArticleCategory.cs @@ -0,0 +1,7 @@ +namespace Wave.Data; + +public class ArticleCategory { + public int Id { get; set; } + public required Article Article { get; set; } + public required Category Category { get; set; } +} \ No newline at end of file diff --git a/Wave/Data/Category.cs b/Wave/Data/Category.cs new file mode 100644 index 0000000..edef76a --- /dev/null +++ b/Wave/Data/Category.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; + +namespace Wave.Data; +// We want to order categories based on their Color/Role, +// so more important ones can be displayed first +public enum CategoryColors { + Primary = 1, + Dangerous = 5, + Important = 10, + Informative = 15, + Secondary = 20, + Default = 25, + Extra = 50, +} + +public class Category { + public Guid Id { get; set; } + + [MaxLength(128)] + public required string Name { get; set; } + public CategoryColors Color { get; set; } = CategoryColors.Default; +} \ No newline at end of file diff --git a/Wave/Data/Migrations/postgres/20240126112644_Categories.Designer.cs b/Wave/Data/Migrations/postgres/20240126112644_Categories.Designer.cs new file mode 100644 index 0000000..a8bf220 --- /dev/null +++ b/Wave/Data/Migrations/postgres/20240126112644_Categories.Designer.cs @@ -0,0 +1,465 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Wave.Data; + +#nullable disable + +namespace Wave.Data.Migrations.postgres +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240126112644_Categories")] + partial class Categories + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .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.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("Wave.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AboutTheAuthor") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Biography") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)"); + + b.Property("BiographyHtml") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FullName") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + 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("Wave.Data.Article", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Body") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyHtml") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastModified") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("now()"); + + b.Property("PublishDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReviewerId") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("ReviewerId"); + + b.ToTable("Articles", (string)null); + }); + + modelBuilder.Entity("Wave.Data.ArticleCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ArticleId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("CategoryId"); + + b.ToTable("ArticleCategories", (string)null); + }); + + modelBuilder.Entity("Wave.Data.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Color") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(25); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Wave.Data.ProfilePicture", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApplicationUserId") + .HasColumnType("text"); + + b.Property("ImageId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ProfilePictures", (string)null); + }); + + 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("Wave.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Wave.Data.ApplicationUser", 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("Wave.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Wave.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Wave.Data.Article", b => + { + b.HasOne("Wave.Data.ApplicationUser", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Wave.Data.ApplicationUser", "Reviewer") + .WithMany() + .HasForeignKey("ReviewerId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Author"); + + b.Navigation("Reviewer"); + }); + + modelBuilder.Entity("Wave.Data.ArticleCategory", b => + { + b.HasOne("Wave.Data.Article", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Wave.Data.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Wave.Data.ProfilePicture", b => + { + b.HasOne("Wave.Data.ApplicationUser", null) + .WithOne("ProfilePicture") + .HasForeignKey("Wave.Data.ProfilePicture", "ApplicationUserId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Wave.Data.ApplicationUser", b => + { + b.Navigation("ProfilePicture"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Wave/Data/Migrations/postgres/20240126112644_Categories.cs b/Wave/Data/Migrations/postgres/20240126112644_Categories.cs new file mode 100644 index 0000000..a98845c --- /dev/null +++ b/Wave/Data/Migrations/postgres/20240126112644_Categories.cs @@ -0,0 +1,157 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Wave.Data.Migrations.postgres; + +/// +public partial class Categories : Migration { + /// + protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.DropForeignKey( + name: "FK_Article_AspNetUsers_AuthorId", + table: "Article"); + + migrationBuilder.DropForeignKey( + name: "FK_Article_AspNetUsers_ReviewerId", + table: "Article"); + + migrationBuilder.DropPrimaryKey( + name: "PK_Article", + table: "Article"); + + migrationBuilder.RenameTable( + name: "Article", + newName: "Articles"); + + migrationBuilder.RenameIndex( + name: "IX_Article_ReviewerId", + table: "Articles", + newName: "IX_Articles_ReviewerId"); + + migrationBuilder.RenameIndex( + name: "IX_Article_AuthorId", + table: "Articles", + newName: "IX_Articles_AuthorId"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Articles", + table: "Articles", + column: "Id"); + + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), + Color = table.Column(type: "integer", nullable: false, defaultValue: 25) + }, + constraints: table => { table.PrimaryKey("PK_Categories", x => x.Id); }); + + migrationBuilder.CreateTable( + name: "ArticleCategories", + columns: table => new { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", + NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ArticleId = table.Column(type: "uuid", nullable: false), + CategoryId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => { + table.PrimaryKey("PK_ArticleCategories", x => x.Id); + table.ForeignKey( + name: "FK_ArticleCategories_Articles_ArticleId", + column: x => x.ArticleId, + principalTable: "Articles", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_ArticleCategories_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_ArticleCategories_ArticleId", + table: "ArticleCategories", + column: "ArticleId"); + + migrationBuilder.CreateIndex( + name: "IX_ArticleCategories_CategoryId", + table: "ArticleCategories", + column: "CategoryId"); + + migrationBuilder.AddForeignKey( + name: "FK_Articles_AspNetUsers_AuthorId", + table: "Articles", + column: "AuthorId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Articles_AspNetUsers_ReviewerId", + table: "Articles", + column: "ReviewerId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropForeignKey( + name: "FK_Articles_AspNetUsers_AuthorId", + table: "Articles"); + + migrationBuilder.DropForeignKey( + name: "FK_Articles_AspNetUsers_ReviewerId", + table: "Articles"); + + migrationBuilder.DropTable( + name: "ArticleCategories"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropPrimaryKey( + name: "PK_Articles", + table: "Articles"); + + migrationBuilder.RenameTable( + name: "Articles", + newName: "Article"); + + migrationBuilder.RenameIndex( + name: "IX_Articles_ReviewerId", + table: "Article", + newName: "IX_Article_ReviewerId"); + + migrationBuilder.RenameIndex( + name: "IX_Articles_AuthorId", + table: "Article", + newName: "IX_Article_AuthorId"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Article", + table: "Article", + column: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_Article_AspNetUsers_AuthorId", + table: "Article", + column: "AuthorId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Article_AspNetUsers_ReviewerId", + table: "Article", + column: "ReviewerId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } +} \ No newline at end of file diff --git a/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs b/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs index e137dc3..0c78ce2 100644 --- a/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs +++ b/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs @@ -262,8 +262,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsDeleted") .HasColumnType("boolean"); - b.Property("LastModified") - .IsRequired() + b.Property("LastModified") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") .HasDefaultValueSql("now()"); @@ -288,7 +287,51 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ReviewerId"); - b.ToTable("Article"); + b.ToTable("Articles", (string)null); + }); + + modelBuilder.Entity("Wave.Data.ArticleCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ArticleId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.HasIndex("CategoryId"); + + b.ToTable("ArticleCategories", (string)null); + }); + + modelBuilder.Entity("Wave.Data.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Color") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(25); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); }); modelBuilder.Entity("Wave.Data.ProfilePicture", b => @@ -382,6 +425,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Reviewer"); }); + modelBuilder.Entity("Wave.Data.ArticleCategory", b => + { + b.HasOne("Wave.Data.Article", "Article") + .WithMany() + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Wave.Data.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Article"); + + b.Navigation("Category"); + }); + modelBuilder.Entity("Wave.Data.ProfilePicture", b => { b.HasOne("Wave.Data.ApplicationUser", null) diff --git a/Wave/Program.cs b/Wave/Program.cs index 54943e6..3d74f6e 100644 --- a/Wave/Program.cs +++ b/Wave/Program.cs @@ -63,6 +63,7 @@ .AddPolicy("ArticleEditPermissions", p => p.RequireRole("Author", "Admin")) .AddPolicy("ArticleReviewPermissions", p => p.RequireRole("Reviewer", "Admin")) .AddPolicy("ArticleDeletePermissions", p => p.RequireRole("Moderator", "Admin")) + .AddPolicy("CategoryManagePermissions", p => p.RequireRole("Admin")) .AddPolicy("RoleAssignPermissions", p => p.RequireRole("Admin")) .AddPolicy("ArticleEditOrReviewPermissions", p => p.RequireRole("Author", "Reviewer", "Admin"));