diff --git a/Wave/Components/Pages/CategoryView.razor b/Wave/Components/Pages/CategoryView.razor index ad7c623..bf0794a 100644 --- a/Wave/Components/Pages/CategoryView.razor +++ b/Wave/Components/Pages/CategoryView.razor @@ -45,7 +45,7 @@ if (Category != null) return; Category = await context.Set() - .IgnoreAutoIncludes().IgnoreQueryFilters() + .IgnoreAutoIncludes() .Include(c => c.Articles).ThenInclude(a => a.Categories) .Include(c => c.Articles).ThenInclude(a => a.Author) .AsSplitQuery() diff --git a/Wave/Components/Pages/UserView.razor b/Wave/Components/Pages/UserView.razor index 99ff8f5..88178be 100644 --- a/Wave/Components/Pages/UserView.razor +++ b/Wave/Components/Pages/UserView.razor @@ -94,14 +94,10 @@ // Find user if (Id is not null) { - var now = DateTimeOffset.UtcNow; User = await context.Users - .IgnoreAutoIncludes().IgnoreQueryFilters() - .Include(u => u.Articles - .Where(a => !a.IsDeleted && - a.Status == ArticleStatus.Published && - a.PublishDate <= now)) - .ThenInclude(a => a.Categories) + .IgnoreAutoIncludes() + .Include(u => u.Links) + .Include(u => u.Articles).ThenInclude(a => a.Categories) .AsSplitQuery() .FirstOrDefaultAsync(u => u.Id == Id.ToString()); } diff --git a/Wave/Data/ApplicationDbContext.cs b/Wave/Data/ApplicationDbContext.cs index be680f5..d4c66a4 100644 --- a/Wave/Data/ApplicationDbContext.cs +++ b/Wave/Data/ApplicationDbContext.cs @@ -55,7 +55,11 @@ public class ApplicationDbContext(DbContextOptions options article.Property(a => a.BodyPlain).HasDefaultValue(""); - article.HasQueryFilter(a => !a.IsDeleted && a.Status >= ArticleStatus.Published && a.PublishDate <= DateTimeOffset.UtcNow); + article.Property(a => a.CanBePublic) + .HasComputedColumnSql( + $"\"{nameof(Article.IsDeleted)}\" = false AND \"{nameof(Article.Status)}\" = {(int)ArticleStatus.Published}", true); + + article.HasQueryFilter(a => a.CanBePublic && a.PublishDate <= DateTimeOffset.UtcNow); article.ToTable("Articles"); }); @@ -74,7 +78,8 @@ public class ApplicationDbContext(DbContextOptions options articleCategory => { articleCategory.HasKey(ac => ac.Id); articleCategory.ToTable("ArticleCategories"); - articleCategory.HasQueryFilter(ac => !ac.Article.IsDeleted); + articleCategory.HasQueryFilter(ac => + ac.Article.CanBePublic && ac.Article.PublishDate <= DateTimeOffset.UtcNow); }); category.ToTable("Categories"); @@ -95,7 +100,7 @@ public class ApplicationDbContext(DbContextOptions options newsletter.Property(a => a.DistributionDateTime) .HasConversion(dateTimeOffsetUtcConverter); - newsletter.HasQueryFilter(n => !n.Article.IsDeleted); + newsletter.HasQueryFilter(n => n.Article.CanBePublic && n.Article.PublishDate <= DateTimeOffset.UtcNow); newsletter.ToTable("Newsletter"); }); builder.Entity(subscriber => { diff --git a/Wave/Data/Article.cs b/Wave/Data/Article.cs index ef3c8e5..2711ab4 100644 --- a/Wave/Data/Article.cs +++ b/Wave/Data/Article.cs @@ -16,6 +16,9 @@ public class Article : ISoftDelete { public Guid Id { get; set; } public bool IsDeleted { get; set; } + // Computed + public bool CanBePublic { get; set; } + [MaxLength(256)] public required string Title { get; set; } public required string Body { get; set; } diff --git a/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs b/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs index 9edd2c5..ca06d3f 100644 --- a/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs +++ b/Wave/Data/Migrations/postgres/ApplicationDbContextModelSnapshot.cs @@ -325,6 +325,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasDefaultValue(""); + b.Property("CanBePublic") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"IsDeleted\" = false AND \"Status\" = 2", true); + b.Property("CreationDate") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") diff --git a/Wave/Data/Migrations/postgres/Article_CanBePublic_Computed.Designer.cs b/Wave/Data/Migrations/postgres/Article_CanBePublic_Computed.Designer.cs new file mode 100644 index 0000000..a4ceb2d --- /dev/null +++ b/Wave/Data/Migrations/postgres/Article_CanBePublic_Computed.Designer.cs @@ -0,0 +1,716 @@ +// +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("20240419191448_Article_CanBePublic_Computed")] + partial class Article_CanBePublic_Computed + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:default-case-insensitive", "und-u-kf-upper-ks-level1,und-u-kf-upper-ks-level1,icu,False") + .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.ApiClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApiKeyKey") + .HasColumnType("character varying(128)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("ApiKeyKey"); + + b.ToTable("ApiClaim"); + }); + + modelBuilder.Entity("Wave.Data.ApiKey", b => + { + b.Property("Key") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("OwnerName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Key"); + + b.ToTable("ApiKey"); + }); + + 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("ContactEmail") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ContactPhone") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactPhoneBusiness") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContactWebsite") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + 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("BodyPlain") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue(""); + + b.Property("CanBePublic") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("boolean") + .HasComputedColumnSql("\"IsDeleted\" = false AND \"Status\" = 2", true); + + 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("Slug") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasDefaultValue(""); + + 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.ArticleImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArticleId") + .HasColumnType("uuid"); + + b.Property("ImageDescription") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId"); + + b.ToTable("Images", (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)") + .UseCollation("default-case-insensitive"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Wave.Data.EmailNewsletter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ArticleId") + .HasColumnType("uuid"); + + b.Property("DistributionDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsSend") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ArticleId") + .IsUnique(); + + b.ToTable("Newsletter", (string)null); + }); + + modelBuilder.Entity("Wave.Data.EmailSubscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("default-case-insensitive"); + + b.Property("Language") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(8) + .HasColumnType("character varying(8)") + .HasDefaultValue("en-US"); + + b.Property("LastMailOpened") + .HasColumnType("timestamp with time zone"); + + b.Property("LastMailReceived") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UnsubscribeReason") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Unsubscribed") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("Unsubscribed"); + + b.ToTable("NewsletterSubscribers", (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("Wave.Data.UserLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ApplicationUserId") + .HasColumnType("text"); + + b.Property("UrlString") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId"); + + b.ToTable("UserLink"); + }); + + 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.ApiClaim", b => + { + b.HasOne("Wave.Data.ApiKey", null) + .WithMany("ApiClaims") + .HasForeignKey("ApiKeyKey") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Wave.Data.Article", b => + { + b.HasOne("Wave.Data.ApplicationUser", "Author") + .WithMany("Articles") + .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.ArticleImage", b => + { + b.HasOne("Wave.Data.Article", null) + .WithMany("Images") + .HasForeignKey("ArticleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Wave.Data.EmailNewsletter", b => + { + b.HasOne("Wave.Data.Article", "Article") + .WithOne() + .HasForeignKey("Wave.Data.EmailNewsletter", "ArticleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Article"); + }); + + 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.UserLink", b => + { + b.HasOne("Wave.Data.ApplicationUser", null) + .WithMany("Links") + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Wave.Data.ApiKey", b => + { + b.Navigation("ApiClaims"); + }); + + modelBuilder.Entity("Wave.Data.ApplicationUser", b => + { + b.Navigation("Articles"); + + b.Navigation("Links"); + + b.Navigation("ProfilePicture"); + }); + + modelBuilder.Entity("Wave.Data.Article", b => + { + b.Navigation("Images"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Wave/Data/Migrations/postgres/Article_CanBePublic_Computed.cs b/Wave/Data/Migrations/postgres/Article_CanBePublic_Computed.cs new file mode 100644 index 0000000..53b262d --- /dev/null +++ b/Wave/Data/Migrations/postgres/Article_CanBePublic_Computed.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Wave.Data.Migrations.postgres; + +/// +public partial class Article_CanBePublic_Computed : Migration { + /// + protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.AddColumn( + name: "CanBePublic", + table: "Articles", + type: "boolean", + nullable: false, + computedColumnSql: "\"IsDeleted\" = false AND \"Status\" = 2", + stored: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropColumn( + name: "CanBePublic", + table: "Articles"); + } +} \ No newline at end of file