diff --git a/Moonlight/App/Database/DataContext.cs b/Moonlight/App/Database/DataContext.cs index 1789594..e4b3f60 100644 --- a/Moonlight/App/Database/DataContext.cs +++ b/Moonlight/App/Database/DataContext.cs @@ -27,10 +27,11 @@ public class DataContext : DbContext public DbSet Coupons { get; set; } public DbSet CouponUses { get; set; } - // Posts + // Community public DbSet Posts { get; set; } public DbSet PostComments { get; set; } public DbSet PostLikes { get; set; } + public DbSet WordFilters { get; set; } public DataContext(ConfigService configService) { diff --git a/Moonlight/App/Database/Entities/Community/WordFilter.cs b/Moonlight/App/Database/Entities/Community/WordFilter.cs new file mode 100644 index 0000000..8d08115 --- /dev/null +++ b/Moonlight/App/Database/Entities/Community/WordFilter.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Database.Entities.Community; + +public class WordFilter +{ + public int Id { get; set; } + public string Filter { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.Designer.cs b/Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.Designer.cs new file mode 100644 index 0000000..7f0deef --- /dev/null +++ b/Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.Designer.cs @@ -0,0 +1,554 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20231028214520_AddedWordFilter")] + partial class AddedWordFilter + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.2"); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorId") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PostId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("PostId"); + + b.ToTable("PostComments"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PostId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("PostLikes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.WordFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Filter") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WordFilters"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Coupon", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Percent") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Coupons"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CouponId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CouponId"); + + b.HasIndex("UserId"); + + b.ToTable("CouponUses"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.ToTable("GiftCodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCodeUse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GiftCodeId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GiftCodeId"); + + b.HasIndex("UserId"); + + b.ToTable("GiftCodeUses"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("MaxPerUser") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("REAL"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Stock") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConfigJsonOverride") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Nickname") + .HasColumnType("TEXT"); + + b.Property("OwnerId") + .HasColumnType("INTEGER"); + + b.Property("ProductId") + .HasColumnType("INTEGER"); + + b.Property("RenewAt") + .HasColumnType("TEXT"); + + b.Property("Suspended") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ProductId"); + + b.ToTable("Services"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.ServiceShare", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ServiceId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ServiceId"); + + b.HasIndex("UserId"); + + b.ToTable("ServiceShares"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Price") + .HasColumnType("REAL"); + + b.Property("Text") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Avatar") + .HasColumnType("TEXT"); + + b.Property("Balance") + .HasColumnType("REAL"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Flags") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("INTEGER"); + + b.Property("TokenValidTimestamp") + .HasColumnType("TEXT"); + + b.Property("TotpKey") + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.Post", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.Community.Post", null) + .WithMany("Comments") + .HasForeignKey("PostId"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostLike", b => + { + b.HasOne("Moonlight.App.Database.Entities.Community.Post", null) + .WithMany("Likes") + .HasForeignKey("PostId"); + + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.Coupon", "Coupon") + .WithMany() + .HasForeignKey("CouponId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", null) + .WithMany("CouponUses") + .HasForeignKey("UserId"); + + b.Navigation("Coupon"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCodeUse", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.GiftCode", "GiftCode") + .WithMany() + .HasForeignKey("GiftCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", null) + .WithMany("GiftCodeUses") + .HasForeignKey("UserId"); + + b.Navigation("GiftCode"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Product", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.Store.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.ServiceShare", b => + { + b.HasOne("Moonlight.App.Database.Entities.Store.Service", null) + .WithMany("Shares") + .HasForeignKey("ServiceId"); + + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Transaction", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", null) + .WithMany("Transactions") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.Post", b => + { + b.Navigation("Comments"); + + b.Navigation("Likes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b => + { + b.Navigation("Shares"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Navigation("CouponUses"); + + b.Navigation("GiftCodeUses"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.cs b/Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.cs new file mode 100644 index 0000000..4ff13e1 --- /dev/null +++ b/Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddedWordFilter : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "WordFilters", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Filter = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WordFilters", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WordFilters"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs index c759c09..d6c7ed5 100644 --- a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs +++ b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs @@ -105,6 +105,21 @@ namespace Moonlight.App.Database.Migrations b.ToTable("PostLikes"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Community.WordFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Filter") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WordFilters"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b => { b.Property("Id") diff --git a/Moonlight/App/Models/Forms/Admin/Community/AddWordFilter.cs b/Moonlight/App/Models/Forms/Admin/Community/AddWordFilter.cs new file mode 100644 index 0000000..b059a13 --- /dev/null +++ b/Moonlight/App/Models/Forms/Admin/Community/AddWordFilter.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.App.Models.Forms.Admin.Community; + +public class AddWordFilter +{ + [Required(ErrorMessage = "You need to specify a filter")] + [Description( + "This filters all posts and comments created using this regex. If any match is found it will block the action")] + public string Filter { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/App/Models/Forms/Admin/Community/EditWordFilter.cs b/Moonlight/App/Models/Forms/Admin/Community/EditWordFilter.cs new file mode 100644 index 0000000..c87cbc1 --- /dev/null +++ b/Moonlight/App/Models/Forms/Admin/Community/EditWordFilter.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.App.Models.Forms.Admin.Community; + +public class EditWordFilter +{ + [Required(ErrorMessage = "You need to specify a filter")] + [Description( + "This filters all posts and comments created using this regex. If any match is found it will block the action")] + public string Filter { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/App/Services/Community/PostService.cs b/Moonlight/App/Services/Community/PostService.cs index 24378ff..f29b94a 100644 --- a/Moonlight/App/Services/Community/PostService.cs +++ b/Moonlight/App/Services/Community/PostService.cs @@ -15,17 +15,29 @@ public class PostService private readonly Repository PostRepository; private readonly Repository PostLikeRepository; private readonly Repository PostCommentRepository; + private readonly Repository WordFilterRepository; - public PostService(Repository postRepository, Repository postLikeRepository, Repository postCommentRepository) + public PostService( + Repository postRepository, + Repository postLikeRepository, + Repository postCommentRepository, + Repository wordFilterRepository) { PostRepository = postRepository; PostLikeRepository = postLikeRepository; PostCommentRepository = postCommentRepository; + WordFilterRepository = wordFilterRepository; } // Posts public async Task Create(User user, string title, string content, PostType type) { + if(await CheckTextForBadWords(title)) + throw new DisplayException("Bad word detected. Please follow the community rules"); + + if(await CheckTextForBadWords(content)) + throw new DisplayException("Bad word detected. Please follow the community rules"); + var post = new Post() { Author = user, @@ -43,6 +55,12 @@ public class PostService public async Task Update(Post post, string title, string content) { + if(await CheckTextForBadWords(title)) + throw new DisplayException("Bad word detected. Please follow the community rules"); + + if(await CheckTextForBadWords(content)) + throw new DisplayException("Bad word detected. Please follow the community rules"); + post.Title = title; post.Content = content; post.UpdatedAt = DateTime.UtcNow; @@ -95,6 +113,9 @@ public class PostService if (!Regex.IsMatch(content, "^[ a-zA-Z0-9äöüßÄÖÜẞ,.;_\\n\\t-]+$")) throw new DisplayException("Illegal characters in comment content"); + if(await CheckTextForBadWords(content)) + throw new DisplayException("Bad word detected. Please follow the community rules"); + //TODO: Swear word filter var comment = new PostComment() @@ -157,4 +178,23 @@ public class PostService await Events.OnPostLiked.InvokeAsync(postWithLikes); } } + + // Utils + private Task CheckTextForBadWords(string input) // This method checks for bad words using the filters added by an admin + { + var filters = WordFilterRepository + .Get() + .Select(x => x.Filter) + .ToArray(); + + //TODO: Add timer for regex matching to create warnings + + foreach (var filter in filters) + { + if (Regex.IsMatch(input, filter)) + return Task.FromResult(true); + } + + return Task.FromResult(false); + } } \ No newline at end of file diff --git a/Moonlight/Shared/Components/Community/PostView.razor b/Moonlight/Shared/Components/Community/PostView.razor index c004343..3f34246 100644 --- a/Moonlight/Shared/Components/Community/PostView.razor +++ b/Moonlight/Shared/Components/Community/PostView.razor @@ -57,7 +57,14 @@ diff --git a/Moonlight/Shared/Components/Forms/AutoCrud.razor b/Moonlight/Shared/Components/Forms/AutoCrud.razor index 15e4dd1..e6b5bcc 100644 --- a/Moonlight/Shared/Components/Forms/AutoCrud.razor +++ b/Moonlight/Shared/Components/Forms/AutoCrud.razor @@ -54,7 +54,7 @@