Added test logo file. Started adding community posts and comments
This commit is contained in:
parent
a29dc8257e
commit
d98e8ef0f8
18 changed files with 1254 additions and 4 deletions
|
@ -1,5 +1,6 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Database.Entities.Community;
|
||||||
using Moonlight.App.Database.Entities.Store;
|
using Moonlight.App.Database.Entities.Store;
|
||||||
using Moonlight.App.Database.Entities.Tickets;
|
using Moonlight.App.Database.Entities.Tickets;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
|
@ -26,6 +27,11 @@ public class DataContext : DbContext
|
||||||
public DbSet<Coupon> Coupons { get; set; }
|
public DbSet<Coupon> Coupons { get; set; }
|
||||||
public DbSet<CouponUse> CouponUses { get; set; }
|
public DbSet<CouponUse> CouponUses { get; set; }
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
public DbSet<Post> Posts { get; set; }
|
||||||
|
public DbSet<PostComment> PostComments { get; set; }
|
||||||
|
public DbSet<PostLike> PostLikes { get; set; }
|
||||||
|
|
||||||
public DataContext(ConfigService configService)
|
public DataContext(ConfigService configService)
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
}
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,94 @@ namespace Moonlight.App.Database.Migrations
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.2");
|
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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Category", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
@ -299,6 +387,47 @@ namespace Moonlight.App.Database.Migrations
|
||||||
b.ToTable("Users");
|
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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.CouponUse", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Moonlight.App.Database.Entities.Store.Coupon", "Coupon")
|
b.HasOne("Moonlight.App.Database.Entities.Store.Coupon", "Coupon")
|
||||||
|
@ -381,6 +510,13 @@ namespace Moonlight.App.Database.Migrations
|
||||||
.HasForeignKey("UserId");
|
.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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Store.Service", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Shares");
|
b.Navigation("Shares");
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Database.Entities.Community;
|
||||||
using Moonlight.App.Database.Entities.Store;
|
using Moonlight.App.Database.Entities.Store;
|
||||||
using Moonlight.App.Event.Args;
|
using Moonlight.App.Event.Args;
|
||||||
|
|
||||||
|
@ -12,4 +13,10 @@ public class Events
|
||||||
public static EventHandler<MailVerificationEventArgs> OnUserMailVerify;
|
public static EventHandler<MailVerificationEventArgs> OnUserMailVerify;
|
||||||
public static EventHandler<Service> OnServiceOrdered;
|
public static EventHandler<Service> OnServiceOrdered;
|
||||||
public static EventHandler<TransactionCreatedEventArgs> OnTransactionCreated;
|
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
|
// This will replace every placeholder with the respective value if specified in the model
|
||||||
// For example:
|
// For example:
|
||||||
// A instance of the user model has been passed in the 'models' parameter of the function.
|
// 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,
|
AdminSessions = 1002,
|
||||||
AdminUsersEdit = 1003,
|
AdminUsersEdit = 1003,
|
||||||
AdminTickets = 1004,
|
AdminTickets = 1004,
|
||||||
|
AdminCommunity = 1030,
|
||||||
AdminStore = 1900,
|
AdminStore = 1900,
|
||||||
AdminViewExceptions = 1999,
|
AdminViewExceptions = 1999,
|
||||||
AdminRoot = 2000
|
AdminRoot = 2000
|
||||||
|
|
135
Moonlight/App/Services/Community/PostService.cs
Normal file
135
Moonlight/App/Services/Community/PostService.cs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
public PostService(Repository<Post> postRepository, Repository<PostLike> postLikeRepository, Repository<PostComment> postCommentRepository)
|
||||||
|
{
|
||||||
|
PostRepository = postRepository;
|
||||||
|
PostLikeRepository = postLikeRepository;
|
||||||
|
PostCommentRepository = postCommentRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
public async Task<Post> Create(User user, string title, string content, PostType type)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
post.Title = title;
|
||||||
|
post.Content = content;
|
||||||
|
|
||||||
|
PostRepository.Update(post);
|
||||||
|
|
||||||
|
await Events.OnPostUpdated.InvokeAsync(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(Post post)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,6 @@
|
||||||
<Folder Include="App\Http\Requests\" />
|
<Folder Include="App\Http\Requests\" />
|
||||||
<Folder Include="App\Http\Resources\" />
|
<Folder Include="App\Http\Resources\" />
|
||||||
<Folder Include="storage\logs\" />
|
<Folder Include="storage\logs\" />
|
||||||
<Folder Include="wwwroot\img\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -8,6 +8,7 @@ using Moonlight.App.Helpers.LogMigrator;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
using Moonlight.App.Services.Background;
|
using Moonlight.App.Services.Background;
|
||||||
|
using Moonlight.App.Services.Community;
|
||||||
using Moonlight.App.Services.Interop;
|
using Moonlight.App.Services.Interop;
|
||||||
using Moonlight.App.Services.ServiceManage;
|
using Moonlight.App.Services.ServiceManage;
|
||||||
using Moonlight.App.Services.Store;
|
using Moonlight.App.Services.Store;
|
||||||
|
@ -58,6 +59,9 @@ builder.Services.AddScoped<StoreGiftService>();
|
||||||
builder.Services.AddSingleton<StorePaymentService>();
|
builder.Services.AddSingleton<StorePaymentService>();
|
||||||
builder.Services.AddScoped<TransactionService>();
|
builder.Services.AddScoped<TransactionService>();
|
||||||
|
|
||||||
|
// Services / Community
|
||||||
|
builder.Services.AddScoped<PostService>();
|
||||||
|
|
||||||
// Services / Users
|
// Services / Users
|
||||||
builder.Services.AddScoped<UserService>();
|
builder.Services.AddScoped<UserService>();
|
||||||
builder.Services.AddScoped<UserAuthService>();
|
builder.Services.AddScoped<UserAuthService>();
|
||||||
|
|
190
Moonlight/Shared/Components/Community/PostView.razor
Normal file
190
Moonlight/Shared/Components/Community/PostView.razor
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
@using Moonlight.App.Database.Entities.Community
|
||||||
|
@using Ganss.Xss
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Community
|
||||||
|
|
||||||
|
@inject Repository<Post> PostRepository
|
||||||
|
@inject IdentityService IdentityService
|
||||||
|
@inject PostService PostService
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<a href="#" class="text-gray-800 text-hover-primary fs-4 fw-bold">@(Post.Author.Username)</a>
|
||||||
|
<span class="text-gray-500 fw-semibold d-block">@(Formatter.FormatAgoFromDateTime(Post.CreatedAt))</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-toolbar">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="fs-6 fw-normal text-gray-700">
|
||||||
|
@{
|
||||||
|
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" : "")">
|
||||||
|
<i class="bx bx-heart fs-2 me-1"></i>
|
||||||
|
@(LikesCount) Like(s)
|
||||||
|
</a>
|
||||||
|
</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>
|
||||||
|
</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.."></textarea>
|
||||||
|
<WButton OnClick="CreateComment" Text="Comment" CssClasses="btn btn-primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public Post Post { 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 = "";
|
||||||
|
|
||||||
|
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
|
||||||
|
.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 InvokeAsync(StateHasChanged);
|
||||||
|
await UpdateCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteComment(PostComment comment)
|
||||||
|
{
|
||||||
|
await PostService.DeleteComment(Post, comment);
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await UpdateCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,8 @@
|
||||||
|
|
||||||
<div class="app-sidebar flex-column @(Layout.ShowMobileSidebar ? "drawer drawer-start drawer-on" : "")">
|
<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">
|
<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">
|
<a href="/" 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="/img/logo.png" class="h-50px d-none d-sm-inline app-sidebar-logo-default">
|
||||||
<img alt="Logo" src="/metronic8/demo38/assets/media/logos/demo38-dark.svg" class="h-20px h-lg-25px theme-dark-show">
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="app-sidebar-navs flex-column-fluid py-6" id="kt_app_sidebar_navs">
|
<div class="app-sidebar-navs flex-column-fluid py-6" id="kt_app_sidebar_navs">
|
||||||
|
|
33
Moonlight/Shared/Views/Community/Index.razor
Normal file
33
Moonlight/Shared/Views/Community/Index.razor
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
@page "/community"
|
||||||
|
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Database.Entities.Community
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.Shared.Components.Community
|
||||||
|
|
||||||
|
@inject Repository<Post> PostRepository
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
@foreach (var post in Posts)
|
||||||
|
{
|
||||||
|
<PostView Post="post" />
|
||||||
|
<div class="mb-10"></div>
|
||||||
|
}
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
private Post[] Posts;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader _)
|
||||||
|
{
|
||||||
|
Posts = PostRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Author)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
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 |
Loading…
Reference in a new issue