Merge pull request #330 from Moonlight-Panel/AddCommunity
Added community tab
This commit is contained in:
commit
c3acb4898e
42 changed files with 2638 additions and 12 deletions
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.Community;
|
||||
using Moonlight.App.Database.Entities.Store;
|
||||
using Moonlight.App.Database.Entities.Tickets;
|
||||
using Moonlight.App.Services;
|
||||
|
@ -26,6 +27,12 @@ public class DataContext : DbContext
|
|||
public DbSet<Coupon> Coupons { get; set; }
|
||||
public DbSet<CouponUse> CouponUses { get; set; }
|
||||
|
||||
// Community
|
||||
public DbSet<Post> Posts { get; set; }
|
||||
public DbSet<PostComment> PostComments { get; set; }
|
||||
public DbSet<PostLike> PostLikes { get; set; }
|
||||
public DbSet<WordFilter> WordFilters { get; set; }
|
||||
|
||||
public DataContext(ConfigService configService)
|
||||
{
|
||||
ConfigService = configService;
|
||||
|
|
16
Moonlight/App/Database/Entities/Community/Post.cs
Normal file
16
Moonlight/App/Database/Entities/Community/Post.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using Moonlight.App.Database.Enums;
|
||||
|
||||
namespace Moonlight.App.Database.Entities.Community;
|
||||
|
||||
public class Post
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } = "";
|
||||
public string Content { get; set; } = "";
|
||||
public User Author { get; set; }
|
||||
public PostType Type { get; set; }
|
||||
public List<PostComment> Comments { get; set; } = new();
|
||||
public List<PostLike> Likes { get; set; } = new();
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
10
Moonlight/App/Database/Entities/Community/PostComment.cs
Normal file
10
Moonlight/App/Database/Entities/Community/PostComment.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Moonlight.App.Database.Entities.Community;
|
||||
|
||||
public class PostComment
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Content { get; set; } = "";
|
||||
public User Author { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
8
Moonlight/App/Database/Entities/Community/PostLike.cs
Normal file
8
Moonlight/App/Database/Entities/Community/PostLike.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Database.Entities.Community;
|
||||
|
||||
public class PostLike
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public User User { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
7
Moonlight/App/Database/Entities/Community/WordFilter.cs
Normal file
7
Moonlight/App/Database/Entities/Community/WordFilter.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Moonlight.App.Database.Entities.Community;
|
||||
|
||||
public class WordFilter
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Filter { get; set; } = "";
|
||||
}
|
8
Moonlight/App/Database/Enums/PostType.cs
Normal file
8
Moonlight/App/Database/Enums/PostType.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Database.Enums;
|
||||
|
||||
public enum PostType
|
||||
{
|
||||
Project = 0,
|
||||
Announcement = 1,
|
||||
Event = 2
|
||||
}
|
539
Moonlight/App/Database/Migrations/20231027105412_AddPostsModels.Designer.cs
generated
Normal file
539
Moonlight/App/Database/Migrations/20231027105412_AddPostsModels.Designer.cs
generated
Normal file
|
@ -0,0 +1,539 @@
|
|||
// <auto-generated />
|
||||
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("20231027105412_AddPostsModels")]
|
||||
partial class AddPostsModels
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AuthorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AuthorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PostId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PostId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PostId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PostLikes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Coupon", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Amount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Percent")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Coupons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CouponId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Amount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("Value")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("GiftCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCodeUse", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GiftCodeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConfigJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Duration")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxPerUser")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("Price")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Stock")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Products");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConfigJsonOverride")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ProductId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("RenewAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ServiceId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Price")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Text")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Transaction");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("Balance")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Flags")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Permissions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("TokenValidTimestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TotpKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPostsModels : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Posts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Title = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Content = table.Column<string>(type: "TEXT", nullable: false),
|
||||
AuthorId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Posts", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Posts_Users_AuthorId",
|
||||
column: x => x.AuthorId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PostComments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Content = table.Column<string>(type: "TEXT", nullable: false),
|
||||
AuthorId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
PostId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PostComments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PostComments_Posts_PostId",
|
||||
column: x => x.PostId,
|
||||
principalTable: "Posts",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_PostComments_Users_AuthorId",
|
||||
column: x => x.AuthorId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PostLikes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
PostId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PostLikes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PostLikes_Posts_PostId",
|
||||
column: x => x.PostId,
|
||||
principalTable: "Posts",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_PostLikes_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PostComments_AuthorId",
|
||||
table: "PostComments",
|
||||
column: "AuthorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PostComments_PostId",
|
||||
table: "PostComments",
|
||||
column: "PostId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PostLikes_PostId",
|
||||
table: "PostLikes",
|
||||
column: "PostId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PostLikes_UserId",
|
||||
table: "PostLikes",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Posts_AuthorId",
|
||||
table: "Posts",
|
||||
column: "AuthorId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PostComments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PostLikes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Posts");
|
||||
}
|
||||
}
|
||||
}
|
554
Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.Designer.cs
generated
Normal file
554
Moonlight/App/Database/Migrations/20231028214520_AddedWordFilter.Designer.cs
generated
Normal file
|
@ -0,0 +1,554 @@
|
|||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AuthorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AuthorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PostId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PostId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Filter")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WordFilters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Categories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Coupon", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Amount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Percent")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Coupons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CouponId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Amount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("Value")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("GiftCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.GiftCodeUse", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GiftCodeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConfigJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Duration")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxPerUser")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("Price")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Stock")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Products");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConfigJsonOverride")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ProductId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("RenewAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ServiceId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Price")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("Text")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Transaction");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<double>("Balance")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Flags")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Permissions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("TokenValidTimestamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TotpKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Moonlight.App.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedWordFilter : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WordFilters",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Filter = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WordFilters", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "WordFilters");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,109 @@ namespace Moonlight.App.Database.Migrations
|
|||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Community.Post", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AuthorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AuthorId");
|
||||
|
||||
b.ToTable("Posts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Community.PostComment", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AuthorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PostId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("PostId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Filter")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WordFilters");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -299,6 +402,47 @@ namespace Moonlight.App.Database.Migrations
|
|||
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")
|
||||
|
@ -381,6 +525,13 @@ namespace Moonlight.App.Database.Migrations
|
|||
.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");
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.Community;
|
||||
using Moonlight.App.Database.Entities.Store;
|
||||
using Moonlight.App.Event.Args;
|
||||
|
||||
|
@ -12,4 +13,10 @@ public class Events
|
|||
public static EventHandler<MailVerificationEventArgs> OnUserMailVerify;
|
||||
public static EventHandler<Service> OnServiceOrdered;
|
||||
public static EventHandler<TransactionCreatedEventArgs> OnTransactionCreated;
|
||||
public static EventHandler<Post> OnPostCreated;
|
||||
public static EventHandler<Post> OnPostUpdated;
|
||||
public static EventHandler<Post> OnPostDeleted;
|
||||
public static EventHandler<Post> OnPostLiked;
|
||||
public static EventHandler<PostComment> OnPostCommentCreated;
|
||||
public static EventHandler<PostComment> OnPostCommentDeleted;
|
||||
}
|
|
@ -226,6 +226,34 @@ public static class Formatter
|
|||
};
|
||||
}
|
||||
|
||||
public static string FormatAgoFromDateTime(DateTime dt)
|
||||
{
|
||||
TimeSpan timeSince = DateTime.UtcNow.Subtract(dt);
|
||||
|
||||
if (timeSince.TotalMilliseconds < 1)
|
||||
return "just now";
|
||||
|
||||
if (timeSince.TotalMinutes < 1)
|
||||
return "less than a minute ago";
|
||||
|
||||
if (timeSince.TotalMinutes < 2)
|
||||
return "1 minute ago";
|
||||
|
||||
if (timeSince.TotalMinutes < 60)
|
||||
return Math.Round(timeSince.TotalMinutes) + " minutes ago";
|
||||
|
||||
if (timeSince.TotalHours < 2)
|
||||
return "1 hour ago";
|
||||
|
||||
if (timeSince.TotalHours < 24)
|
||||
return Math.Round(timeSince.TotalHours) + " hours ago";
|
||||
|
||||
if (timeSince.TotalDays < 2)
|
||||
return "1 day ago";
|
||||
|
||||
return Math.Round(timeSince.TotalDays) + " days ago";
|
||||
}
|
||||
|
||||
// This will replace every placeholder with the respective value if specified in the model
|
||||
// For example:
|
||||
// A instance of the user model has been passed in the 'models' parameter of the function.
|
||||
|
|
|
@ -9,6 +9,7 @@ public enum Permission
|
|||
AdminSessions = 1002,
|
||||
AdminUsersEdit = 1003,
|
||||
AdminTickets = 1004,
|
||||
AdminCommunity = 1030,
|
||||
AdminStore = 1900,
|
||||
AdminViewExceptions = 1999,
|
||||
AdminRoot = 2000
|
||||
|
|
12
Moonlight/App/Models/Forms/Admin/Community/AddWordFilter.cs
Normal file
12
Moonlight/App/Models/Forms/Admin/Community/AddWordFilter.cs
Normal file
|
@ -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; } = "";
|
||||
}
|
12
Moonlight/App/Models/Forms/Admin/Community/EditWordFilter.cs
Normal file
12
Moonlight/App/Models/Forms/Admin/Community/EditWordFilter.cs
Normal file
|
@ -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; } = "";
|
||||
}
|
16
Moonlight/App/Models/Forms/Community/AddPostForm.cs
Normal file
16
Moonlight/App/Models/Forms/Community/AddPostForm.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms.Community;
|
||||
|
||||
public class AddPostForm
|
||||
{
|
||||
[Required(ErrorMessage = "You need to enter a title")]
|
||||
[MaxLength(40, ErrorMessage = "The title can only be 40 characters long")]
|
||||
[MinLength(8, ErrorMessage = "The title must at least have 8 characters")]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "You need to enter post content")]
|
||||
[MaxLength(2048, ErrorMessage = "The post content can only be 2048 characters long")]
|
||||
[MinLength(8, ErrorMessage = "The post content must at least have 8 characters")]
|
||||
public string Content { get; set; } = "";
|
||||
}
|
200
Moonlight/App/Services/Community/PostService.cs
Normal file
200
Moonlight/App/Services/Community/PostService.cs
Normal file
|
@ -0,0 +1,200 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.Community;
|
||||
using Moonlight.App.Database.Enums;
|
||||
using Moonlight.App.Event;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Extensions;
|
||||
using Moonlight.App.Repositories;
|
||||
|
||||
namespace Moonlight.App.Services.Community;
|
||||
|
||||
public class PostService
|
||||
{
|
||||
private readonly Repository<Post> PostRepository;
|
||||
private readonly Repository<PostLike> PostLikeRepository;
|
||||
private readonly Repository<PostComment> PostCommentRepository;
|
||||
private readonly Repository<WordFilter> WordFilterRepository;
|
||||
|
||||
public PostService(
|
||||
Repository<Post> postRepository,
|
||||
Repository<PostLike> postLikeRepository,
|
||||
Repository<PostComment> postCommentRepository,
|
||||
Repository<WordFilter> wordFilterRepository)
|
||||
{
|
||||
PostRepository = postRepository;
|
||||
PostLikeRepository = postLikeRepository;
|
||||
PostCommentRepository = postCommentRepository;
|
||||
WordFilterRepository = wordFilterRepository;
|
||||
}
|
||||
|
||||
// Posts
|
||||
public async Task<Post> 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,
|
||||
Title = title,
|
||||
Content = content,
|
||||
Type = type
|
||||
};
|
||||
|
||||
var finishedPost = PostRepository.Add(post);
|
||||
|
||||
await Events.OnPostCreated.InvokeAsync(finishedPost);
|
||||
|
||||
return finishedPost;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
PostRepository.Update(post);
|
||||
|
||||
await Events.OnPostUpdated.InvokeAsync(post);
|
||||
}
|
||||
|
||||
public async Task Delete(Post post)
|
||||
{
|
||||
var postWithData = PostRepository
|
||||
.Get()
|
||||
.Include(x => x.Comments)
|
||||
.Include(x => x.Likes)
|
||||
.First(x => x.Id == post.Id);
|
||||
|
||||
// Cache relational data to delete later on
|
||||
var likes = postWithData.Likes.ToArray();
|
||||
var comments = postWithData.Comments.ToArray();
|
||||
|
||||
// Clear relations
|
||||
postWithData.Comments.Clear();
|
||||
postWithData.Likes.Clear();
|
||||
|
||||
PostRepository.Update(postWithData);
|
||||
|
||||
// Delete relational data
|
||||
foreach (var like in likes)
|
||||
PostLikeRepository.Delete(like);
|
||||
|
||||
foreach (var comment in comments)
|
||||
PostCommentRepository.Delete(comment);
|
||||
|
||||
// Now delete the post itself
|
||||
PostRepository.Delete(post);
|
||||
await Events.OnPostDeleted.InvokeAsync(post);
|
||||
}
|
||||
|
||||
// Comments
|
||||
public async Task<PostComment> CreateComment(Post post, User user, string content)
|
||||
{
|
||||
// As the comment feature has no edit form or model to validate we do the validation here
|
||||
if (string.IsNullOrEmpty(content))
|
||||
throw new DisplayException("Comment content cannot be empty");
|
||||
|
||||
if (content.Length > 1024)
|
||||
throw new DisplayException("Comment content cannot be longer than 1024 characters");
|
||||
|
||||
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()
|
||||
{
|
||||
Author = user,
|
||||
Content = content
|
||||
};
|
||||
|
||||
post.Comments.Add(comment);
|
||||
PostRepository.Update(post);
|
||||
|
||||
await Events.OnPostCommentCreated.InvokeAsync(comment);
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
public async Task DeleteComment(Post post, PostComment comment)
|
||||
{
|
||||
var postWithComments = PostRepository
|
||||
.Get()
|
||||
.Include(x => x.Comments)
|
||||
.First(x => x.Id == post.Id);
|
||||
|
||||
var commentToRemove = postWithComments.Comments.First(x => x.Id == comment.Id);
|
||||
postWithComments.Comments.Remove(commentToRemove);
|
||||
|
||||
PostRepository.Update(postWithComments);
|
||||
PostCommentRepository.Delete(commentToRemove);
|
||||
|
||||
await Events.OnPostCommentCreated.InvokeAsync(commentToRemove);
|
||||
}
|
||||
|
||||
// Other
|
||||
public async Task ToggleLike(Post post, User user)
|
||||
{
|
||||
var postWithLikes = PostRepository
|
||||
.Get()
|
||||
.Include(x => x.Likes)
|
||||
.ThenInclude(x => x.User)
|
||||
.First(x => x.Id == post.Id);
|
||||
|
||||
var userLike = postWithLikes.Likes.FirstOrDefault(x => x.User.Id == user.Id);
|
||||
|
||||
if (userLike != null) // Check if person already liked
|
||||
{
|
||||
postWithLikes.Likes.Remove(userLike);
|
||||
|
||||
PostRepository.Update(postWithLikes);
|
||||
PostLikeRepository.Delete(userLike);
|
||||
}
|
||||
else
|
||||
{
|
||||
postWithLikes.Likes.Add(new()
|
||||
{
|
||||
User = user
|
||||
});
|
||||
|
||||
PostRepository.Update(postWithLikes);
|
||||
|
||||
await Events.OnPostLiked.InvokeAsync(postWithLikes);
|
||||
}
|
||||
}
|
||||
|
||||
// Utils
|
||||
private Task<bool> 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);
|
||||
}
|
||||
}
|
|
@ -11,11 +11,11 @@ public class ModalService
|
|||
JsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task Show(string id)
|
||||
public async Task Show(string id, bool focus = true) // Focus can be specified to fix issues with other components
|
||||
{
|
||||
try
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.modals.show", id);
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.modals.show", id, focus);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
<Folder Include="App\Http\Requests\" />
|
||||
<Folder Include="App\Http\Resources\" />
|
||||
<Folder Include="storage\logs\" />
|
||||
<Folder Include="wwwroot\img\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
<PackageReference Include="BlazorTable" Version="1.17.0" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="8.0.746" />
|
||||
<PackageReference Include="JWT" Version="10.1.1" />
|
||||
<PackageReference Include="MailKit" Version="4.2.0" />
|
||||
<PackageReference Include="Mappy.Net" Version="1.0.2" />
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<script src="/js/moonlight.js"></script>
|
||||
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
||||
<script src="/js/sweetalert2.js"></script>
|
||||
<script src="/js/ckeditor.js"></script>
|
||||
<script src="/_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -8,6 +8,7 @@ using Moonlight.App.Helpers.LogMigrator;
|
|||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services;
|
||||
using Moonlight.App.Services.Background;
|
||||
using Moonlight.App.Services.Community;
|
||||
using Moonlight.App.Services.Interop;
|
||||
using Moonlight.App.Services.ServiceManage;
|
||||
using Moonlight.App.Services.Store;
|
||||
|
@ -58,6 +59,9 @@ builder.Services.AddScoped<StoreGiftService>();
|
|||
builder.Services.AddSingleton<StorePaymentService>();
|
||||
builder.Services.AddScoped<TransactionService>();
|
||||
|
||||
// Services / Community
|
||||
builder.Services.AddScoped<PostService>();
|
||||
|
||||
// Services / Users
|
||||
builder.Services.AddScoped<UserService>();
|
||||
builder.Services.AddScoped<UserAuthService>();
|
||||
|
|
272
Moonlight/Shared/Components/Community/PostView.razor
Normal file
272
Moonlight/Shared/Components/Community/PostView.razor
Normal file
|
@ -0,0 +1,272 @@
|
|||
@using Moonlight.App.Database.Entities.Community
|
||||
@using Ganss.Xss
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Community
|
||||
|
||||
@inject Repository<Post> PostRepository
|
||||
@inject IdentityService IdentityService
|
||||
@inject PostService PostService
|
||||
@inject ToastService ToastService
|
||||
|
||||
<div class="card card-flush">
|
||||
<div class="card-header pt-9">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="symbol symbol-50px me-5">
|
||||
<img src="/api/bucket/avatars/@(Post.Author.Avatar)" class="" alt="">
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<span class="text-gray-800 text-hover-primary fs-4 fw-bold">@(Post.Author.Username)</span>
|
||||
<span class="text-gray-500 fw-semibold d-block">@(Formatter.FormatAgoFromDateTime(Post.CreatedAt))</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Post.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
|
||||
{
|
||||
<div class="card-toolbar">
|
||||
<a @onclick="DeletePost" @onclick:preventDefault href="#" class="text-danger fw-semibold d-block">Remove post</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="fs-6 fw-normal text-gray-700">
|
||||
@if (IsEditing)
|
||||
{
|
||||
<TextEditor @bind-Value="EditContent" InitialContent="@Post.Content" />
|
||||
}
|
||||
else
|
||||
{
|
||||
var sanitizer = new HtmlSanitizer();
|
||||
var content = sanitizer.Sanitize(Post.Content);
|
||||
|
||||
@((MarkupString)content)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer pt-0">
|
||||
<div class="mb-6">
|
||||
<div class="separator separator-solid"></div>
|
||||
<ul class="nav py-3">
|
||||
<li class="nav-item">
|
||||
<a @onclick="ToggleComments" @onclick:preventDefault href="#" class="nav-link btn btn-sm btn-color-gray-600 btn-active-color-primary fw-bold px-4 me-1 @(ShowComments ? "active" : "")">
|
||||
<i class="bx bx-message fs-2 me-1"></i>
|
||||
@(CommentsCount) Comment(s)
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a @onclick="ToggleLike" @onclick:preventDefault href="#" class="nav-link btn btn-sm btn-color-gray-600 btn-active-color-danger fw-bold px-4 me-1 @(HasLiked ? "active" : "")">
|
||||
@if (HasLiked)
|
||||
{
|
||||
<i class="bx bxs-heart fs-2 me-1"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bx bx-heart fs-2 me-1"></i>
|
||||
}
|
||||
@(LikesCount) Like(s)
|
||||
</a>
|
||||
</li>
|
||||
@if (Post.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
|
||||
{
|
||||
<li class="nav-item">
|
||||
<div class="nav-link pt-0">
|
||||
<a @onclick="() => ToggleEdit()" @onclick:preventDefault href="#" class="btn btn-sm btn-color-gray-600 btn-active-color-warning fw-bold px-4 pe-0 @(IsEditing ? "active" : "")">
|
||||
<i class="bx bx-edit fs-2"></i>
|
||||
@(IsEditing ? "Save" : "Edit")
|
||||
</a>
|
||||
|
||||
@if (IsEditing)
|
||||
{
|
||||
<a @onclick="() => ToggleEdit(true)" @onclick:preventDefault href="#" class="btn btn-sm btn-color-gray-600 btn-active-color-warning fw-bold px-4 ps-2 @(IsEditing ? "active" : "")">
|
||||
<i class="bx bx-x fs-2"></i>
|
||||
Cancel
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div class="separator separator-solid mb-1"></div>
|
||||
@if (ShowComments)
|
||||
{
|
||||
<LazyLoader Load="LoadComments">
|
||||
@if (Comments.Any())
|
||||
{
|
||||
foreach (var comment in Comments)
|
||||
{
|
||||
<div class="d-flex pt-6">
|
||||
<div class="symbol symbol-45px me-5">
|
||||
<img src="/api/bucket/avatars/@(comment.Author.Avatar)" alt="">
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-row-fluid">
|
||||
<div class="d-flex align-items-center flex-wrap mb-0">
|
||||
<a href="#" class="text-gray-800 text-hover-primary fw-bold me-6">@(comment.Author.Username)</a>
|
||||
<span class="text-gray-500 fw-semibold fs-7 me-5">@(Formatter.FormatAgoFromDateTime(comment.CreatedAt))</span>
|
||||
|
||||
@if (comment.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
|
||||
{
|
||||
<a @onclick="() => DeleteComment(comment)" @onclick:preventDefault href="#" class="text-danger fw-semibold fs-7">Remove comment</a>
|
||||
}
|
||||
</div>
|
||||
<span class="text-gray-800 fs-6 fw-normal pt-1">
|
||||
@(Formatter.FormatLineBreaks(comment.Content))
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex pt-6 justify-content-center">
|
||||
<span class="fs-5">No comments... yet</span>
|
||||
</div>
|
||||
}
|
||||
<div class="separator separator-solid mt-6"></div>
|
||||
</LazyLoader>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="symbol symbol-35px me-3">
|
||||
<img src="/api/bucket/avatars/@(IdentityService.CurrentUser.Avatar)" alt="">
|
||||
</div>
|
||||
<div class="position-relative w-100">
|
||||
<div class="input-group">
|
||||
<textarea @bind="Comment" type="text" class="form-control form-control-solid border ps-5" placeholder="Write your comment.." style="height: 1vh"></textarea>
|
||||
<WButton OnClick="CreateComment" Text="Comment" CssClasses="btn btn-primary"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public Post Post { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnUpdate { get; set; }
|
||||
|
||||
private int CommentsCount = -1;
|
||||
private int LikesCount = -1;
|
||||
private bool HasLiked = false;
|
||||
|
||||
private bool ShowComments = false;
|
||||
private PostComment[] Comments = Array.Empty<PostComment>();
|
||||
private string Comment = "";
|
||||
|
||||
private bool IsEditing = false;
|
||||
private string EditTitle = "";
|
||||
private string EditContent = "";
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await UpdateCounts();
|
||||
}
|
||||
}
|
||||
|
||||
private Task LoadComments(LazyLoader _)
|
||||
{
|
||||
Comments = PostRepository
|
||||
.Get()
|
||||
.Include(x => x.Comments)
|
||||
.ThenInclude(x => x.Author)
|
||||
.First(x => x.Id == Post.Id)
|
||||
.Comments
|
||||
.OrderBy(x => x.CreatedAt)
|
||||
.ToArray();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task UpdateCounts()
|
||||
{
|
||||
CommentsCount = PostRepository
|
||||
.Get()
|
||||
.Where(x => x.Id == Post.Id)
|
||||
.SelectMany(x => x.Comments)
|
||||
.Count();
|
||||
|
||||
LikesCount = PostRepository
|
||||
.Get()
|
||||
.Where(x => x.Id == Post.Id)
|
||||
.SelectMany(x => x.Likes)
|
||||
.Count();
|
||||
|
||||
HasLiked = PostRepository
|
||||
.Get()
|
||||
.Where(x => x.Id == Post.Id)
|
||||
.SelectMany(x => x.Likes)
|
||||
.Any(x => x.User.Id == IdentityService.CurrentUser.Id);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task CreateComment()
|
||||
{
|
||||
await PostService.CreateComment(Post, IdentityService.CurrentUser, Comment);
|
||||
|
||||
Comment = "";
|
||||
ShowComments = true;
|
||||
await LoadComments(null!);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await UpdateCounts();
|
||||
}
|
||||
|
||||
private async Task DeleteComment(PostComment comment)
|
||||
{
|
||||
await PostService.DeleteComment(Post, comment);
|
||||
|
||||
await LoadComments(null!);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await UpdateCounts();
|
||||
|
||||
await ToastService.Success("Successfully deleted comment");
|
||||
}
|
||||
|
||||
private async Task ToggleComments()
|
||||
{
|
||||
ShowComments = !ShowComments;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
if (!ShowComments)
|
||||
Comments = Array.Empty<PostComment>(); // Clear unused data
|
||||
}
|
||||
|
||||
private async Task ToggleLike()
|
||||
{
|
||||
await PostService.ToggleLike(Post, IdentityService.CurrentUser);
|
||||
await UpdateCounts();
|
||||
}
|
||||
|
||||
private async Task ToggleEdit(bool preventSaving = false)
|
||||
{
|
||||
IsEditing = !IsEditing;
|
||||
|
||||
if (IsEditing)
|
||||
{
|
||||
EditTitle = Post.Title;
|
||||
EditContent = Post.Content;
|
||||
}
|
||||
else if (!preventSaving)
|
||||
{
|
||||
await PostService.Update(Post, EditTitle, EditContent);
|
||||
await ToastService.Success("Successfully saved post");
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private async Task DeletePost()
|
||||
{
|
||||
await PostService.Delete(Post);
|
||||
await ToastService.Success("Successfully deleted post");
|
||||
|
||||
if (OnUpdate != null)
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@
|
|||
<SmartForm Model="CreateForm" OnValidSubmit="FinishCreate">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<AutoForm Columns="6" Model="CreateForm"/>
|
||||
<AutoForm Columns="@(CreateForm.GetType().GetProperties().Length > 1 ? 6 : 12)" Model="CreateForm"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -72,7 +72,7 @@
|
|||
<SmartForm Model="UpdateForm" OnValidSubmit="FinishUpdate">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<AutoForm Columns="6" Model="UpdateForm"/>
|
||||
<AutoForm Columns="@(UpdateForm.GetType().GetProperties().Length > 1 ? 6 : 12)" Model="UpdateForm"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
175
Moonlight/Shared/Components/Forms/TextEditor.razor
Normal file
175
Moonlight/Shared/Components/Forms/TextEditor.razor
Normal file
|
@ -0,0 +1,175 @@
|
|||
@inject IJSRuntime JsRuntime
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Moonlight.App.Services
|
||||
@using Ganss.Xss
|
||||
@inherits InputBase<string>
|
||||
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
<style>
|
||||
:root {
|
||||
/*
|
||||
Fix from https://ckeditor.com/docs/ckeditor5/latest/installation/integrations/css.html
|
||||
to make the editor work with bootstrap
|
||||
*/
|
||||
--ck-z-default: 100;
|
||||
--ck-z-modal: calc( var(--ck-z-default) + 999 );
|
||||
|
||||
/* Overrides the border radius setting in the theme. */
|
||||
--ck-border-radius: 4px;
|
||||
|
||||
/* Overrides the default font size in the theme. */
|
||||
--ck-font-size-base: 14px;
|
||||
|
||||
/* Helper variables to avoid duplication in the colors. */
|
||||
--ck-custom-background: #1e1e2d;
|
||||
--ck-custom-foreground: hsl(255, 3%, 18%);
|
||||
--ck-custom-border: hsl(300, 1%, 22%);
|
||||
--ck-custom-white: hsl(0, 0%, 100%);
|
||||
|
||||
/* -- Overrides generic colors. ------------------------------------------------------------- */
|
||||
|
||||
--ck-color-base-foreground: var(--ck-custom-background);
|
||||
--ck-color-focus-border: hsl(208, 90%, 62%);
|
||||
--ck-color-text: hsl(0, 0%, 98%);
|
||||
--ck-color-shadow-drop: hsla(0, 0%, 0%, 0.2);
|
||||
--ck-color-shadow-inner: hsla(0, 0%, 0%, 0.1);
|
||||
|
||||
/* -- Overrides the default .ck-button class colors. ---------------------------------------- */
|
||||
|
||||
--ck-color-button-default-background: var(--ck-custom-background);
|
||||
--ck-color-button-default-hover-background: hsl(270, 1%, 22%);
|
||||
--ck-color-button-default-active-background: hsl(270, 2%, 20%);
|
||||
--ck-color-button-default-active-shadow: hsl(270, 2%, 23%);
|
||||
--ck-color-button-default-disabled-background: var(--ck-custom-background);
|
||||
|
||||
--ck-color-button-on-background: var(--ck-custom-foreground);
|
||||
--ck-color-button-on-hover-background: hsl(255, 4%, 16%);
|
||||
--ck-color-button-on-active-background: hsl(255, 4%, 14%);
|
||||
--ck-color-button-on-active-shadow: hsl(240, 3%, 19%);
|
||||
--ck-color-button-on-disabled-background: var(--ck-custom-foreground);
|
||||
|
||||
--ck-color-button-action-background: hsl(168, 76%, 42%);
|
||||
--ck-color-button-action-hover-background: hsl(168, 76%, 38%);
|
||||
--ck-color-button-action-active-background: hsl(168, 76%, 36%);
|
||||
--ck-color-button-action-active-shadow: hsl(168, 75%, 34%);
|
||||
--ck-color-button-action-disabled-background: hsl(168, 76%, 42%);
|
||||
--ck-color-button-action-text: var(--ck-custom-white);
|
||||
|
||||
/* -- Overrides the default .ck-dropdown class colors. -------------------------------------- */
|
||||
|
||||
--ck-color-dropdown-panel-background: var(--ck-custom-background);
|
||||
--ck-color-dropdown-panel-border: var(--ck-custom-foreground);
|
||||
|
||||
/* -- Overrides the default .ck-splitbutton class colors. ----------------------------------- */
|
||||
|
||||
--ck-color-split-button-hover-background: var(--ck-color-button-default-hover-background);
|
||||
--ck-color-split-button-hover-border: var(--ck-custom-foreground);
|
||||
|
||||
/* -- Overrides the default .ck-input class colors. ----------------------------------------- */
|
||||
|
||||
--ck-color-input-background: var(--ck-custom-background);
|
||||
--ck-color-input-border: hsl(257, 3%, 43%);
|
||||
--ck-color-input-text: hsl(0, 0%, 98%);
|
||||
--ck-color-input-disabled-background: hsl(255, 4%, 21%);
|
||||
--ck-color-input-disabled-border: hsl(250, 3%, 38%);
|
||||
--ck-color-input-disabled-text: hsl(0, 0%, 78%);
|
||||
|
||||
/* -- Overrides the default .ck-labeled-field-view class colors. ---------------------------- */
|
||||
|
||||
--ck-color-labeled-field-label-background: var(--ck-custom-background);
|
||||
|
||||
/* -- Overrides the default .ck-list class colors. ------------------------------------------ */
|
||||
|
||||
--ck-color-list-background: var(--ck-custom-background);
|
||||
--ck-color-list-button-hover-background: var(--ck-custom-foreground);
|
||||
--ck-color-list-button-on-background: hsl(208, 88%, 52%);
|
||||
--ck-color-list-button-on-text: var(--ck-custom-white);
|
||||
|
||||
/* -- Overrides the default .ck-balloon-panel class colors. --------------------------------- */
|
||||
|
||||
--ck-color-panel-background: var(--ck-custom-background);
|
||||
--ck-color-panel-border: var(--ck-custom-border);
|
||||
|
||||
/* -- Overrides the default .ck-toolbar class colors. --------------------------------------- */
|
||||
|
||||
--ck-color-toolbar-background: var(--ck-custom-background);
|
||||
--ck-color-toolbar-border: var(--ck-custom-border);
|
||||
|
||||
/* -- Overrides the default .ck-tooltip class colors. --------------------------------------- */
|
||||
|
||||
--ck-color-tooltip-background: hsl(252, 7%, 14%);
|
||||
--ck-color-tooltip-text: hsl(0, 0%, 93%);
|
||||
|
||||
/* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */
|
||||
|
||||
--ck-color-image-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
||||
|
||||
/* -- Overrides the default colors used by the ckeditor5-widget package. -------------------- */
|
||||
|
||||
--ck-color-widget-blurred-border: hsl(0, 0%, 87%);
|
||||
--ck-color-widget-hover-border: hsl(43, 100%, 68%);
|
||||
--ck-color-widget-editable-focus-background: var(--ck-custom-white);
|
||||
|
||||
/* -- Overrides the default colors used by the ckeditor5-link package. ---------------------- */
|
||||
|
||||
--ck-color-link-default: hsl(190, 100%, 75%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="@Id" class="card card-body bg-black @(CssClasses)" @onfocusout="Callback" style="@(Styles)"></div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public string InitialContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string CssClasses { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public string Styles { get; set; } = ""; // We added this parameter to allow custom heights to be set
|
||||
|
||||
private string Id;
|
||||
private bool IsInitialized = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Id = "editor" + GetHashCode();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.textEditor.create", Id);
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.textEditor.set", Id, InitialContent);
|
||||
CurrentValue = InitialContent;
|
||||
IsInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Callback()
|
||||
{
|
||||
if(!IsInitialized)
|
||||
return;
|
||||
|
||||
var html = await JsRuntime.InvokeAsync<string>("moonlight.textEditor.get", Id);
|
||||
|
||||
var sanitizer = new HtmlSanitizer();
|
||||
var sanitized = sanitizer.Sanitize(html);
|
||||
|
||||
if(sanitized != html)
|
||||
Logger.Warn($"XSS attempt by {IdentityService.CurrentUserNullable?.Username ?? "Guest"}: {html}", "security");
|
||||
|
||||
CurrentValue = sanitized;
|
||||
}
|
||||
|
||||
protected override bool TryParseValueFromString(string? value, out string result, out string? validationErrorMessage)
|
||||
{
|
||||
result = value;
|
||||
validationErrorMessage = "";
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
@using Moonlight.App.Models.Forms.Community
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Community
|
||||
@using Moonlight.App.Database.Enums
|
||||
|
||||
@inject PostService PostService
|
||||
@inject IdentityService IdentityService
|
||||
@inject ToastService ToastService
|
||||
|
||||
<SmartModal @ref="Modal" CssClasses="modal-fullscreen">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fs-3">Create a new post</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<SmartForm Model="Form" OnValidSubmit="Submit">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title</label>
|
||||
<div class="form-text fs-5 mb-2 mt-0">
|
||||
This title is used for the preview of posts. It will not be shown in a regular post view
|
||||
</div>
|
||||
<input @bind="Form.Title" class="form-control form-control-solid-bg"/>
|
||||
</div>
|
||||
<div>
|
||||
<TextEditor @bind-Value="Form.Content" InitialContent="A well written post content from you" Styles="height: 55vh"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</SmartModal>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public Func<Task>? OnUpdate { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public PostType PostType { get; set; }
|
||||
|
||||
private AddPostForm Form = new();
|
||||
private SmartModal Modal;
|
||||
|
||||
public async Task Show()
|
||||
{
|
||||
Form = new();
|
||||
await Modal.Show(false);
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await PostService.Create(
|
||||
IdentityService.CurrentUser,
|
||||
Form.Title,
|
||||
Form.Content,
|
||||
PostType
|
||||
);
|
||||
|
||||
await Modal.Hide();
|
||||
await ToastService.Success("Successfully created post");
|
||||
|
||||
if (OnUpdate != null)
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<div class="card mb-5 mb-xl-10">
|
||||
<div class="card-body pt-0 pb-0">
|
||||
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/community">
|
||||
<i class="bx bx-sm bx-group me-2"></i> Overview
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/community/filter">
|
||||
<i class="bx bx-sm bx-filter-alt me-2"></i> Filter
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Index { get; set; }
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<div class="card">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold text-gray-900">Post channels</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body pt-5">
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="symbol symbol-40px me-5">
|
||||
<i class="bx bx-sm bx-envelope text-gray-500"></i>
|
||||
</div>
|
||||
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
||||
<a href="/community" class="text-gray-800 fs-5 fw-bold text-active-primary @(Index == 0 ? "active" : "")">Announcements</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator separator-dashed my-4"></div>
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="symbol symbol-40px me-5">
|
||||
<i class="bx bx-sm bx-calendar-event text-gray-500"></i>
|
||||
</div>
|
||||
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
||||
<a href="/community/events" class="text-gray-800 fs-5 fw-bold text-active-primary @(Index == 1 ? "active" : "")">Events</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator separator-dashed my-4"></div>
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="symbol symbol-40px me-5">
|
||||
<i class="bx bx-sm bxs-inbox text-gray-500"></i>
|
||||
</div>
|
||||
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
|
||||
<a href="/community/projects" class="text-gray-800 fs-5 fw-bold text-active-primary @(Index == 2 ? "active" : "")">Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Index { get; set; }
|
||||
}
|
|
@ -6,9 +6,8 @@
|
|||
|
||||
<div class="app-sidebar flex-column @(Layout.ShowMobileSidebar ? "drawer drawer-start drawer-on" : "")">
|
||||
<div class="app-sidebar-header d-flex flex-stack d-none d-lg-flex pt-8 pb-2">
|
||||
<a href="/metronic8/demo38/../demo38/index.html" class="app-sidebar-logo">
|
||||
<img alt="Logo" src="/metronic8/demo38/assets/media/logos/demo38.svg" class="h-25px d-none d-sm-inline app-sidebar-logo-default theme-light-show">
|
||||
<img alt="Logo" src="/metronic8/demo38/assets/media/logos/demo38-dark.svg" class="h-20px h-lg-25px theme-dark-show">
|
||||
<a href="/" class="app-sidebar-logo">
|
||||
<img alt="Logo" src="/img/logo.png" class="h-50px d-none d-sm-inline app-sidebar-logo-default">
|
||||
</a>
|
||||
</div>
|
||||
<div class="app-sidebar-navs flex-column-fluid py-6" id="kt_app_sidebar_navs">
|
||||
|
@ -95,6 +94,17 @@
|
|||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="menu-item">
|
||||
<a class="menu-link " href="/admin/community">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-sm bx-group"></i>
|
||||
</span>
|
||||
<span class="menu-title">
|
||||
Community
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,12 +27,12 @@
|
|||
Id = GetHashCode();
|
||||
}
|
||||
|
||||
public async Task Show()
|
||||
public async Task Show(bool focus = true) // Focus can be specified to fix issues with other components
|
||||
{
|
||||
ShouldShow = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await ModalService.Show("modal" + Id);
|
||||
await ModalService.Show("modal" + Id, focus);
|
||||
}
|
||||
|
||||
public async Task Hide()
|
||||
|
|
38
Moonlight/Shared/Views/Admin/Community/Filter.razor
Normal file
38
Moonlight/Shared/Views/Admin/Community/Filter.razor
Normal file
|
@ -0,0 +1,38 @@
|
|||
@page "/admin/community/filter"
|
||||
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using BlazorTable
|
||||
@using Moonlight.App.Database.Entities.Community
|
||||
@using Moonlight.App.Models.Forms.Admin.Community
|
||||
@using Moonlight.App.Repositories
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminCommunity)]
|
||||
|
||||
<AdminCommunityNavigation Index="1" />
|
||||
|
||||
<div class="card card-body border-primary fs-5 mt-5">
|
||||
To protect from trollers and toxic people you can configure words using
|
||||
regex expressions to block automatically to ensure no one can write bad things in the community tab.
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<AutoCrud TItem="WordFilter"
|
||||
TCreateForm="AddWordFilter"
|
||||
TUpdateForm="EditWordFilter"
|
||||
Title="Manage word filter"
|
||||
Load="LoadData">
|
||||
<Column TableItem="WordFilter" Field="@(x => x.Id)" Title="Id" Sortable="false" Filterable="true" />
|
||||
<Column TableItem="WordFilter" Field="@(x => x.Filter)" Title="Filter" Sortable="false" Filterable="true" />
|
||||
</AutoCrud>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private WordFilter[] LoadData(Repository<WordFilter> repository)
|
||||
{
|
||||
return repository
|
||||
.Get()
|
||||
.ToArray();
|
||||
}
|
||||
}
|
8
Moonlight/Shared/Views/Admin/Community/Index.razor
Normal file
8
Moonlight/Shared/Views/Admin/Community/Index.razor
Normal file
|
@ -0,0 +1,8 @@
|
|||
@page "/admin/community"
|
||||
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminCommunity)]
|
||||
|
||||
<AdminCommunityNavigation Index="0" />
|
|
@ -3,8 +3,12 @@
|
|||
@using Moonlight.App.Database.Entities.Store
|
||||
@using Moonlight.App.Repositories
|
||||
@using BlazorTable
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using Moonlight.App.Models.Forms.Admin.Store
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminStore)]
|
||||
|
||||
@inject Repository<Coupon> CouponRepository
|
||||
|
||||
<AdminStoreNavigation Index="1"/>
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
@using Moonlight.App.Models.Forms.Admin.Store
|
||||
@using Moonlight.App.Repositories
|
||||
@using BlazorTable
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminStore)]
|
||||
|
||||
@inject Repository<GiftCode> GiftCodeRepository
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
@page "/admin/store"
|
||||
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminStore)]
|
||||
|
||||
<AdminStoreNavigation Index="0"/>
|
66
Moonlight/Shared/Views/Community/Events.razor
Normal file
66
Moonlight/Shared/Views/Community/Events.razor
Normal file
|
@ -0,0 +1,66 @@
|
|||
@page "/community/events"
|
||||
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities.Community
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Database.Enums
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.Shared.Components.Community
|
||||
@using Moonlight.Shared.Components.Modals.Community
|
||||
|
||||
@inject IdentityService IdentityService
|
||||
@inject Repository<Post> PostRepository
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-12 mb-5">
|
||||
<CommunityNavigation Index="1"/>
|
||||
|
||||
@if (IdentityService.Permissions[Permission.AdminCommunity])
|
||||
{
|
||||
<div class="card card-body mt-5">
|
||||
<button @onclick="() => CreateModal.Show()" class="btn btn-success">Create new post</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-10 col-12">
|
||||
<div class="card border-primary mb-5">
|
||||
<div class="card-body fs-5">
|
||||
Planned events and current happenings can be found here.
|
||||
If you want to know what will happen in the future or is going on now have a look at the posts below
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@foreach (var post in Posts)
|
||||
{
|
||||
<PostView Post="post" OnUpdate="() => LazyLoader.Reload()"/>
|
||||
<div class="mb-10"></div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (IdentityService.Permissions[Permission.AdminCommunity])
|
||||
{
|
||||
<CreatePostModal @ref="CreateModal" OnUpdate="() => LazyLoader.Reload()" PostType="PostType.Event"/>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
private LazyLoader LazyLoader;
|
||||
private CreatePostModal CreateModal;
|
||||
private Post[] Posts;
|
||||
|
||||
private Task Load(LazyLoader _)
|
||||
{
|
||||
Posts = PostRepository
|
||||
.Get()
|
||||
.Include(x => x.Author)
|
||||
.Where(x => x.Type == PostType.Event)
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.ToArray();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
67
Moonlight/Shared/Views/Community/Index.razor
Normal file
67
Moonlight/Shared/Views/Community/Index.razor
Normal file
|
@ -0,0 +1,67 @@
|
|||
@page "/community"
|
||||
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities.Community
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Database.Enums
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.Shared.Components.Community
|
||||
@using Moonlight.Shared.Components.Modals.Community
|
||||
|
||||
@inject Repository<Post> PostRepository
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-12 mb-5">
|
||||
<CommunityNavigation Index="0"/>
|
||||
|
||||
@if (IdentityService.Permissions[Permission.AdminCommunity])
|
||||
{
|
||||
<div class="card card-body mt-5">
|
||||
<button @onclick="() => CreateModal.Show()" class="btn btn-success">Create new post</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-10 col-12">
|
||||
<div class="card border-primary mb-5">
|
||||
<div class="card-body fs-5">
|
||||
These announcements provide you with the latest news and information.
|
||||
The posts here have been created by an admin and can contain valuable information
|
||||
so consider reading it from time to time
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@foreach (var post in Posts)
|
||||
{
|
||||
<PostView Post="post" OnUpdate="() => LazyLoader.Reload()"/>
|
||||
<div class="mb-10"></div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (IdentityService.Permissions[Permission.AdminCommunity])
|
||||
{
|
||||
<CreatePostModal @ref="CreateModal" OnUpdate="() => LazyLoader.Reload()" PostType="PostType.Announcement"/>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
private LazyLoader LazyLoader;
|
||||
private CreatePostModal CreateModal;
|
||||
private Post[] Posts;
|
||||
|
||||
private Task Load(LazyLoader _)
|
||||
{
|
||||
Posts = PostRepository
|
||||
.Get()
|
||||
.Include(x => x.Author)
|
||||
.Where(x => x.Type == PostType.Announcement)
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.ToArray();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
58
Moonlight/Shared/Views/Community/Projects.razor
Normal file
58
Moonlight/Shared/Views/Community/Projects.razor
Normal file
|
@ -0,0 +1,58 @@
|
|||
@page "/community/projects"
|
||||
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities.Community
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Database.Enums
|
||||
@using Moonlight.Shared.Components.Community
|
||||
@using Moonlight.Shared.Components.Modals.Community
|
||||
|
||||
@inject Repository<Post> PostRepository
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-12 mb-5">
|
||||
<CommunityNavigation Index="2"/>
|
||||
|
||||
<div class="card card-body mt-5">
|
||||
<button @onclick="() => CreateModal.Show()" class="btn btn-success">Create new post</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10 col-12">
|
||||
<div class="card border-primary mb-5">
|
||||
<div class="card-body fs-5">
|
||||
You have a interesting project or a fun game server you want to share with the community?
|
||||
You can share it here. Please keep in mind to follow basic rules and dont offend anyone.
|
||||
Be nice and respectful
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@foreach (var post in Posts)
|
||||
{
|
||||
<PostView Post="post" OnUpdate="() => LazyLoader.Reload()"/>
|
||||
<div class="mb-10"></div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreatePostModal @ref="CreateModal" OnUpdate="() => LazyLoader.Reload()" PostType="PostType.Project"/>
|
||||
|
||||
@code
|
||||
{
|
||||
private LazyLoader LazyLoader;
|
||||
private CreatePostModal CreateModal;
|
||||
private Post[] Posts;
|
||||
|
||||
private Task Load(LazyLoader _)
|
||||
{
|
||||
Posts = PostRepository
|
||||
.Get()
|
||||
.Include(x => x.Author)
|
||||
.Where(x => x.Type == PostType.Project)
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
.ToArray();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
4
Moonlight/wwwroot/css/utils.css
vendored
4
Moonlight/wwwroot/css/utils.css
vendored
|
@ -19,3 +19,7 @@
|
|||
.blur {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.ck-powered-by {
|
||||
display: none;
|
||||
}
|
BIN
Moonlight/wwwroot/img/logo.png
vendored
Normal file
BIN
Moonlight/wwwroot/img/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
7
Moonlight/wwwroot/js/ckeditor.js
vendored
Normal file
7
Moonlight/wwwroot/js/ckeditor.js
vendored
Normal file
File diff suppressed because one or more lines are too long
36
Moonlight/wwwroot/js/moonlight.js
vendored
36
Moonlight/wwwroot/js/moonlight.js
vendored
|
@ -23,9 +23,12 @@ window.moonlight = {
|
|||
}
|
||||
},
|
||||
modals: {
|
||||
show: function (id)
|
||||
show: function (id, focus)
|
||||
{
|
||||
let modal = new bootstrap.Modal(document.getElementById(id));
|
||||
let modal = new bootstrap.Modal(document.getElementById(id), {
|
||||
focus: focus
|
||||
});
|
||||
|
||||
modal.show();
|
||||
},
|
||||
hide: function (id)
|
||||
|
@ -105,5 +108,34 @@ window.moonlight = {
|
|||
|
||||
return text;
|
||||
}
|
||||
},
|
||||
textEditor: {
|
||||
create: function(id)
|
||||
{
|
||||
BalloonEditor
|
||||
.create(document.getElementById(id), {
|
||||
toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote' ],
|
||||
heading: {
|
||||
options: [
|
||||
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
|
||||
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
|
||||
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' }
|
||||
]
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
get: function (id)
|
||||
{
|
||||
let editor = document.getElementById(id).ckeditorInstance;
|
||||
return editor.getData();
|
||||
},
|
||||
set: function (id, data)
|
||||
{
|
||||
let editor = document.getElementById(id).ckeditorInstance;
|
||||
editor.setData(data);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue