✨ Wallet, payment, developer apps, feature flags of publishers
♻️ Simplified the permission check of chat room, realm, publishers
This commit is contained in:
parent
9576870373
commit
d7d4fde06a
@ -1,5 +1,6 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
|
using DysonNetwork.Sphere.Publisher;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
@ -43,9 +44,11 @@ public class AppDatabase(
|
|||||||
|
|
||||||
public DbSet<Activity.Activity> Activities { get; set; }
|
public DbSet<Activity.Activity> Activities { get; set; }
|
||||||
|
|
||||||
public DbSet<Post.Publisher> Publishers { get; set; }
|
public DbSet<Publisher.Publisher> Publishers { get; set; }
|
||||||
public DbSet<Post.PublisherMember> PublisherMembers { get; set; }
|
public DbSet<PublisherMember> PublisherMembers { get; set; }
|
||||||
public DbSet<Post.PublisherSubscription> PublisherSubscriptions { get; set; }
|
public DbSet<PublisherSubscription> PublisherSubscriptions { get; set; }
|
||||||
|
public DbSet<PublisherFeature> PublisherFeatures { get; set; }
|
||||||
|
|
||||||
public DbSet<Post.Post> Posts { get; set; }
|
public DbSet<Post.Post> Posts { get; set; }
|
||||||
public DbSet<Post.PostReaction> PostReactions { get; set; }
|
public DbSet<Post.PostReaction> PostReactions { get; set; }
|
||||||
public DbSet<Post.PostTag> PostTags { get; set; }
|
public DbSet<Post.PostTag> PostTags { get; set; }
|
||||||
@ -65,6 +68,13 @@ public class AppDatabase(
|
|||||||
public DbSet<Sticker.Sticker> Stickers { get; set; }
|
public DbSet<Sticker.Sticker> Stickers { get; set; }
|
||||||
public DbSet<Sticker.StickerPack> StickerPacks { get; set; }
|
public DbSet<Sticker.StickerPack> StickerPacks { get; set; }
|
||||||
|
|
||||||
|
public DbSet<Wallet.Wallet> Wallets { get; set; }
|
||||||
|
public DbSet<Wallet.WalletPocket> WalletPockets { get; set; }
|
||||||
|
public DbSet<Wallet.Order> PaymentOrders { get; set; }
|
||||||
|
public DbSet<Wallet.Transaction> PaymentTransactions { get; set; }
|
||||||
|
|
||||||
|
public DbSet<Developer.CustomApp> CustomApps { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App"));
|
var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App"));
|
||||||
@ -138,24 +148,24 @@ public class AppDatabase(
|
|||||||
.WithMany(a => a.IncomingRelationships)
|
.WithMany(a => a.IncomingRelationships)
|
||||||
.HasForeignKey(r => r.RelatedId);
|
.HasForeignKey(r => r.RelatedId);
|
||||||
|
|
||||||
modelBuilder.Entity<Post.PublisherMember>()
|
modelBuilder.Entity<PublisherMember>()
|
||||||
.HasKey(pm => new { pm.PublisherId, pm.AccountId });
|
.HasKey(pm => new { pm.PublisherId, pm.AccountId });
|
||||||
modelBuilder.Entity<Post.PublisherMember>()
|
modelBuilder.Entity<PublisherMember>()
|
||||||
.HasOne(pm => pm.Publisher)
|
.HasOne(pm => pm.Publisher)
|
||||||
.WithMany(p => p.Members)
|
.WithMany(p => p.Members)
|
||||||
.HasForeignKey(pm => pm.PublisherId)
|
.HasForeignKey(pm => pm.PublisherId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Post.PublisherMember>()
|
modelBuilder.Entity<PublisherMember>()
|
||||||
.HasOne(pm => pm.Account)
|
.HasOne(pm => pm.Account)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(pm => pm.AccountId)
|
.HasForeignKey(pm => pm.AccountId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Post.PublisherSubscription>()
|
modelBuilder.Entity<PublisherSubscription>()
|
||||||
.HasOne(ps => ps.Publisher)
|
.HasOne(ps => ps.Publisher)
|
||||||
.WithMany(p => p.Subscriptions)
|
.WithMany(p => p.Subscriptions)
|
||||||
.HasForeignKey(ps => ps.PublisherId)
|
.HasForeignKey(ps => ps.PublisherId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Post.PublisherSubscription>()
|
modelBuilder.Entity<PublisherSubscription>()
|
||||||
.HasOne(ps => ps.Account)
|
.HasOne(ps => ps.Account)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(ps => ps.AccountId)
|
.HasForeignKey(ps => ps.AccountId)
|
||||||
|
@ -10,7 +10,7 @@ namespace DysonNetwork.Sphere.Chat;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/chat")]
|
[Route("/chat")]
|
||||||
public class ChatRoomController(AppDatabase db, FileService fs, ChatRoomService crs) : ControllerBase
|
public class ChatRoomController(AppDatabase db, FileService fs, ChatRoomService crs, RealmService rs) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("{id:guid}")]
|
[HttpGet("{id:guid}")]
|
||||||
public async Task<ActionResult<ChatRoom>> GetChatRoom(Guid id)
|
public async Task<ActionResult<ChatRoom>> GetChatRoom(Guid id)
|
||||||
@ -168,13 +168,9 @@ public class ChatRoomController(AppDatabase db, FileService fs, ChatRoomService
|
|||||||
|
|
||||||
if (request.RealmId is not null)
|
if (request.RealmId is not null)
|
||||||
{
|
{
|
||||||
var member = await db.RealmMembers
|
if (!await rs.IsMemberWithRole(request.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator))
|
||||||
.Where(m => m.AccountId == currentUser.Id)
|
|
||||||
.Where(m => m.RealmId == request.RealmId)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (member is null || member.Role < RealmMemberRole.Moderator)
|
|
||||||
return StatusCode(403, "You need at least be a moderator to create chat linked to the realm.");
|
return StatusCode(403, "You need at least be a moderator to create chat linked to the realm.");
|
||||||
chatRoom.RealmId = member.RealmId;
|
chatRoom.RealmId = request.RealmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.PictureId is not null)
|
if (request.PictureId is not null)
|
||||||
@ -216,22 +212,11 @@ public class ChatRoomController(AppDatabase db, FileService fs, ChatRoomService
|
|||||||
|
|
||||||
if (chatRoom.RealmId is not null)
|
if (chatRoom.RealmId is not null)
|
||||||
{
|
{
|
||||||
var realmMember = await db.RealmMembers
|
if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator))
|
||||||
.Where(m => m.AccountId == currentUser.Id)
|
|
||||||
.Where(m => m.RealmId == chatRoom.RealmId)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator)
|
|
||||||
return StatusCode(403, "You need at least be a realm moderator to update the chat.");
|
return StatusCode(403, "You need at least be a realm moderator to update the chat.");
|
||||||
}
|
}
|
||||||
else
|
else if (!await crs.IsMemberWithRole(chatRoom.Id, currentUser.Id, ChatMemberRole.Moderator))
|
||||||
{
|
return StatusCode(403, "You need at least be a moderator to update the chat.");
|
||||||
var chatMember = await db.ChatMembers
|
|
||||||
.Where(m => m.AccountId == currentUser.Id)
|
|
||||||
.Where(m => m.ChatRoomId == chatRoom.Id)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (chatMember is null || chatMember.Role < ChatMemberRole.Moderator)
|
|
||||||
return StatusCode(403, "You need at least be a moderator to update the chat.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.RealmId is not null)
|
if (request.RealmId is not null)
|
||||||
{
|
{
|
||||||
@ -287,22 +272,11 @@ public class ChatRoomController(AppDatabase db, FileService fs, ChatRoomService
|
|||||||
|
|
||||||
if (chatRoom.RealmId is not null)
|
if (chatRoom.RealmId is not null)
|
||||||
{
|
{
|
||||||
var realmMember = await db.RealmMembers
|
if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator))
|
||||||
.Where(m => m.AccountId == currentUser.Id)
|
|
||||||
.Where(m => m.RealmId == chatRoom.RealmId)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator)
|
|
||||||
return StatusCode(403, "You need at least be a realm moderator to delete the chat.");
|
return StatusCode(403, "You need at least be a realm moderator to delete the chat.");
|
||||||
}
|
}
|
||||||
else
|
else if (!await crs.IsMemberWithRole(chatRoom.Id, currentUser.Id, ChatMemberRole.Owner))
|
||||||
{
|
return StatusCode(403, "You need at least be the owner to delete the chat.");
|
||||||
var chatMember = await db.ChatMembers
|
|
||||||
.Where(m => m.AccountId == currentUser.Id)
|
|
||||||
.Where(m => m.ChatRoomId == chatRoom.Id)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (chatMember is null || chatMember.Role < ChatMemberRole.Owner)
|
|
||||||
return StatusCode(403, "You need at least be the owner to delete the chat.");
|
|
||||||
}
|
|
||||||
|
|
||||||
db.ChatRooms.Remove(chatRoom);
|
db.ChatRooms.Remove(chatRoom);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Chat;
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
|
|
||||||
public class ChatRoomService(NotificationService nty)
|
public class ChatRoomService(AppDatabase db, NotificationService nty)
|
||||||
{
|
{
|
||||||
public async Task SendInviteNotify(ChatMember member)
|
public async Task SendInviteNotify(ChatMember member)
|
||||||
{
|
{
|
||||||
await nty.SendNotification(member.Account, "invites.chats", "New Chat Invitation", null,
|
await nty.SendNotification(member.Account, "invites.chats", "New Chat Invitation", null,
|
||||||
$"You just got invited to join {member.ChatRoom.Name}");
|
$"You just got invited to join {member.ChatRoom.Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsMemberWithRole(Guid roomId, Guid accountId, ChatMemberRole requiredRole)
|
||||||
|
{
|
||||||
|
var member = await db.ChatMembers
|
||||||
|
.FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == accountId);
|
||||||
|
return member?.Role >= requiredRole;
|
||||||
|
}
|
||||||
}
|
}
|
25
DysonNetwork.Sphere/Developer/CustomApp.cs
Normal file
25
DysonNetwork.Sphere/Developer/CustomApp.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Developer;
|
||||||
|
|
||||||
|
public enum CustomAppStatus
|
||||||
|
{
|
||||||
|
Developing,
|
||||||
|
Staging,
|
||||||
|
Production,
|
||||||
|
Suspended
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomApp : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(1024)] public string Slug { get; set; } = null!;
|
||||||
|
[MaxLength(1024)] public string Name { get; set; } = null!;
|
||||||
|
public CustomAppStatus Status { get; set; } = CustomAppStatus.Developing;
|
||||||
|
public Instant? VerifiedAt { get; set; }
|
||||||
|
[MaxLength(4096)] public string? VerifiedAs { get; set; }
|
||||||
|
|
||||||
|
public Guid PublisherId { get; set; }
|
||||||
|
public Publisher.Publisher Developer { get; set; } = null!;
|
||||||
|
}
|
11
DysonNetwork.Sphere/Developer/CustomAppController.cs
Normal file
11
DysonNetwork.Sphere/Developer/CustomAppController.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using DysonNetwork.Sphere.Publisher;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Developer;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("/developers/apps")]
|
||||||
|
public class CustomAppController(PublisherService ps) : ControllerBase
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
3086
DysonNetwork.Sphere/Migrations/20250514142322_WalletAndPayment.Designer.cs
generated
Normal file
3086
DysonNetwork.Sphere/Migrations/20250514142322_WalletAndPayment.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class WalletAndPayment : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "wallets",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_wallets", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_wallets_accounts_account_id",
|
||||||
|
column: x => x.account_id,
|
||||||
|
principalTable: "accounts",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "payment_transactions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||||
|
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||||
|
type = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
payer_wallet_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
payee_wallet_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_payment_transactions", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_payment_transactions_wallets_payee_wallet_id",
|
||||||
|
column: x => x.payee_wallet_id,
|
||||||
|
principalTable: "wallets",
|
||||||
|
principalColumn: "id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_payment_transactions_wallets_payer_wallet_id",
|
||||||
|
column: x => x.payer_wallet_id,
|
||||||
|
principalTable: "wallets",
|
||||||
|
principalColumn: "id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "wallet_pockets",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||||
|
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
wallet_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_wallet_pockets", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_wallet_pockets_wallets_wallet_id",
|
||||||
|
column: x => x.wallet_id,
|
||||||
|
principalTable: "wallets",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "payment_orders",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
currency = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||||
|
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||||
|
amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
payee_wallet_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
transaction_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_payment_orders", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_payment_orders_payment_transactions_transaction_id",
|
||||||
|
column: x => x.transaction_id,
|
||||||
|
principalTable: "payment_transactions",
|
||||||
|
principalColumn: "id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_payment_orders_wallets_payee_wallet_id",
|
||||||
|
column: x => x.payee_wallet_id,
|
||||||
|
principalTable: "wallets",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_payment_orders_payee_wallet_id",
|
||||||
|
table: "payment_orders",
|
||||||
|
column: "payee_wallet_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_payment_orders_transaction_id",
|
||||||
|
table: "payment_orders",
|
||||||
|
column: "transaction_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_payment_transactions_payee_wallet_id",
|
||||||
|
table: "payment_transactions",
|
||||||
|
column: "payee_wallet_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_payment_transactions_payer_wallet_id",
|
||||||
|
table: "payment_transactions",
|
||||||
|
column: "payer_wallet_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_wallet_pockets_wallet_id",
|
||||||
|
table: "wallet_pockets",
|
||||||
|
column: "wallet_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_wallets_account_id",
|
||||||
|
table: "wallets",
|
||||||
|
column: "account_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "payment_orders");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "wallet_pockets");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "payment_transactions");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "wallets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3209
DysonNetwork.Sphere/Migrations/20250514150441_PublisherFeatures.Designer.cs
generated
Normal file
3209
DysonNetwork.Sphere/Migrations/20250514150441_PublisherFeatures.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class PublisherFeatures : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "publisher_type",
|
||||||
|
table: "publishers",
|
||||||
|
newName: "type");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "custom_apps",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
verified_as = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||||
|
publisher_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_custom_apps", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_custom_apps_publishers_publisher_id",
|
||||||
|
column: x => x.publisher_id,
|
||||||
|
principalTable: "publishers",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "publisher_features",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
flag = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
publisher_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_publisher_features", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_publisher_features_publishers_publisher_id",
|
||||||
|
column: x => x.publisher_id,
|
||||||
|
principalTable: "publishers",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_custom_apps_publisher_id",
|
||||||
|
table: "custom_apps",
|
||||||
|
column: "publisher_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_publisher_features_publisher_id",
|
||||||
|
table: "publisher_features",
|
||||||
|
column: "publisher_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "custom_apps");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "publisher_features");
|
||||||
|
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "type",
|
||||||
|
table: "publishers",
|
||||||
|
newName: "publisher_type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1140,6 +1140,63 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("chat_realtime_call", (string)null);
|
b.ToTable("chat_realtime_call", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("name");
|
||||||
|
|
||||||
|
b.Property<Guid>("PublisherId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("publisher_id");
|
||||||
|
|
||||||
|
b.Property<string>("Slug")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("slug");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<string>("VerifiedAs")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("verified_as");
|
||||||
|
|
||||||
|
b.Property<Instant?>("VerifiedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("verified_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_custom_apps");
|
||||||
|
|
||||||
|
b.HasIndex("PublisherId")
|
||||||
|
.HasDatabaseName("ix_custom_apps_publisher_id");
|
||||||
|
|
||||||
|
b.ToTable("custom_apps", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -1565,7 +1622,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("post_tags", (string)null);
|
b.ToTable("post_tags", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@ -1609,14 +1666,14 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("character varying(128)")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("picture_id");
|
.HasColumnName("picture_id");
|
||||||
|
|
||||||
b.Property<int>("PublisherType")
|
|
||||||
.HasColumnType("integer")
|
|
||||||
.HasColumnName("publisher_type");
|
|
||||||
|
|
||||||
b.Property<Guid?>("RealmId")
|
b.Property<Guid?>("RealmId")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("realm_id");
|
.HasColumnName("realm_id");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("type");
|
||||||
|
|
||||||
b.Property<Instant>("UpdatedAt")
|
b.Property<Instant>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
@ -1643,7 +1700,49 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("publishers", (string)null);
|
b.ToTable("publishers", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<string>("Flag")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("flag");
|
||||||
|
|
||||||
|
b.Property<Guid>("PublisherId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("publisher_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_publisher_features");
|
||||||
|
|
||||||
|
b.HasIndex("PublisherId")
|
||||||
|
.HasDatabaseName("ix_publisher_features_publisher_id");
|
||||||
|
|
||||||
|
b.ToTable("publisher_features", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("PublisherId")
|
b.Property<Guid>("PublisherId")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
@ -1682,7 +1781,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("publisher_members", (string)null);
|
b.ToTable("publisher_members", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherSubscription", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@ -2049,6 +2148,200 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("files", (string)null);
|
b.ToTable("files", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("amount");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("currency");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant>("ExpiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expired_at");
|
||||||
|
|
||||||
|
b.Property<Guid>("PayeeWalletId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("payee_wallet_id");
|
||||||
|
|
||||||
|
b.Property<string>("Remarks")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("remarks");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("status");
|
||||||
|
|
||||||
|
b.Property<Guid?>("TransactionId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("transaction_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_payment_orders");
|
||||||
|
|
||||||
|
b.HasIndex("PayeeWalletId")
|
||||||
|
.HasDatabaseName("ix_payment_orders_payee_wallet_id");
|
||||||
|
|
||||||
|
b.HasIndex("TransactionId")
|
||||||
|
.HasDatabaseName("ix_payment_orders_transaction_id");
|
||||||
|
|
||||||
|
b.ToTable("payment_orders", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("amount");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("currency");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PayeeWalletId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("payee_wallet_id");
|
||||||
|
|
||||||
|
b.Property<Guid?>("PayerWalletId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("payer_wallet_id");
|
||||||
|
|
||||||
|
b.Property<string>("Remarks")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("remarks");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("type");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_payment_transactions");
|
||||||
|
|
||||||
|
b.HasIndex("PayeeWalletId")
|
||||||
|
.HasDatabaseName("ix_payment_transactions_payee_wallet_id");
|
||||||
|
|
||||||
|
b.HasIndex("PayerWalletId")
|
||||||
|
.HasDatabaseName("ix_payment_transactions_payer_wallet_id");
|
||||||
|
|
||||||
|
b.ToTable("payment_transactions", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_wallets");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId")
|
||||||
|
.HasDatabaseName("ix_wallets_account_id");
|
||||||
|
|
||||||
|
b.ToTable("wallets", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric")
|
||||||
|
.HasColumnName("amount");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)")
|
||||||
|
.HasColumnName("currency");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<Guid>("WalletId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("wallet_id");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_wallet_pockets");
|
||||||
|
|
||||||
|
b.HasIndex("WalletId")
|
||||||
|
.HasDatabaseName("ix_wallet_pockets_wallet_id");
|
||||||
|
|
||||||
|
b.ToTable("wallet_pockets", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PostPostCategory", b =>
|
modelBuilder.Entity("PostPostCategory", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("CategoriesId")
|
b.Property<Guid>("CategoriesId")
|
||||||
@ -2437,6 +2730,18 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Sender");
|
b.Navigation("Sender");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomApp", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Developer")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PublisherId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_custom_apps_publishers_publisher_id");
|
||||||
|
|
||||||
|
b.Navigation("Developer");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
||||||
@ -2467,7 +2772,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Restrict)
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
.HasConstraintName("fk_posts_posts_forwarded_post_id");
|
.HasConstraintName("fk_posts_posts_forwarded_post_id");
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
|
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher")
|
||||||
.WithMany("Posts")
|
.WithMany("Posts")
|
||||||
.HasForeignKey("PublisherId")
|
.HasForeignKey("PublisherId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -2496,7 +2801,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
|
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher")
|
||||||
.WithMany("Collections")
|
.WithMany("Collections")
|
||||||
.HasForeignKey("PublisherId")
|
.HasForeignKey("PublisherId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -2527,7 +2832,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Post");
|
b.Navigation("Post");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
@ -2558,7 +2863,19 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Realm");
|
b.Navigation("Realm");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherFeature", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PublisherId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_publisher_features_publishers_publisher_id");
|
||||||
|
|
||||||
|
b.Navigation("Publisher");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherMember", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
@ -2567,7 +2884,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("fk_publisher_members_accounts_account_id");
|
.HasConstraintName("fk_publisher_members_accounts_account_id");
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
|
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher")
|
||||||
.WithMany("Members")
|
.WithMany("Members")
|
||||||
.HasForeignKey("PublisherId")
|
.HasForeignKey("PublisherId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -2579,7 +2896,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Publisher");
|
b.Navigation("Publisher");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherSubscription", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.PublisherSubscription", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
@ -2588,7 +2905,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("fk_publisher_subscriptions_accounts_account_id");
|
.HasConstraintName("fk_publisher_subscriptions_accounts_account_id");
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
|
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher")
|
||||||
.WithMany("Subscriptions")
|
.WithMany("Subscriptions")
|
||||||
.HasForeignKey("PublisherId")
|
.HasForeignKey("PublisherId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -2670,7 +2987,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
|
b.HasOne("DysonNetwork.Sphere.Publisher.Publisher", "Publisher")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PublisherId")
|
.HasForeignKey("PublisherId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -2702,6 +3019,66 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Account");
|
b.Navigation("Account");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PayeeWalletId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_payment_orders_wallets_payee_wallet_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Wallet.Transaction", "Transaction")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TransactionId")
|
||||||
|
.HasConstraintName("fk_payment_orders_payment_transactions_transaction_id");
|
||||||
|
|
||||||
|
b.Navigation("PayeeWallet");
|
||||||
|
|
||||||
|
b.Navigation("Transaction");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Transaction", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PayeeWalletId")
|
||||||
|
.HasConstraintName("fk_payment_transactions_wallets_payee_wallet_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayerWallet")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PayerWalletId")
|
||||||
|
.HasConstraintName("fk_payment_transactions_wallets_payer_wallet_id");
|
||||||
|
|
||||||
|
b.Navigation("PayeeWallet");
|
||||||
|
|
||||||
|
b.Navigation("PayerWallet");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_wallets_accounts_account_id");
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.WalletPocket", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "Wallet")
|
||||||
|
.WithMany("Pockets")
|
||||||
|
.HasForeignKey("WalletId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_wallet_pockets_wallets_wallet_id");
|
||||||
|
|
||||||
|
b.Navigation("Wallet");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PostPostCategory", b =>
|
modelBuilder.Entity("PostPostCategory", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null)
|
b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null)
|
||||||
@ -2801,7 +3178,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Reactions");
|
b.Navigation("Reactions");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Publisher.Publisher", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Collections");
|
b.Navigation("Collections");
|
||||||
|
|
||||||
@ -2818,6 +3195,11 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
b.Navigation("Members");
|
b.Navigation("Members");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Wallet", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Pockets");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
@ -55,7 +54,7 @@ public class Post : ModelBase
|
|||||||
|
|
||||||
[JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!;
|
[JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!;
|
||||||
|
|
||||||
public Publisher Publisher { get; set; } = null!;
|
public Publisher.Publisher Publisher { get; set; } = null!;
|
||||||
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
||||||
public ICollection<PostTag> Tags { get; set; } = new List<PostTag>();
|
public ICollection<PostTag> Tags { get; set; } = new List<PostTag>();
|
||||||
public ICollection<PostCategory> Categories { get; set; } = new List<PostCategory>();
|
public ICollection<PostCategory> Categories { get; set; } = new List<PostCategory>();
|
||||||
@ -88,7 +87,7 @@ public class PostCollection : ModelBase
|
|||||||
[MaxLength(256)] public string? Name { get; set; }
|
[MaxLength(256)] public string? Name { get; set; }
|
||||||
[MaxLength(4096)] public string? Description { get; set; }
|
[MaxLength(4096)] public string? Description { get; set; }
|
||||||
|
|
||||||
public Publisher Publisher { get; set; } = null!;
|
public Publisher.Publisher Publisher { get; set; } = null!;
|
||||||
|
|
||||||
public ICollection<Post> Posts { get; set; } = new List<Post>();
|
public ICollection<Post> Posts { get; set; } = new List<Post>();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
|
using DysonNetwork.Sphere.Publisher;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -11,7 +12,13 @@ namespace DysonNetwork.Sphere.Post;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/posts")]
|
[Route("/posts")]
|
||||||
public class PostController(AppDatabase db, PostService ps, RelationshipService rels, IServiceScopeFactory factory)
|
public class PostController(
|
||||||
|
AppDatabase db,
|
||||||
|
PostService ps,
|
||||||
|
PublisherService pub,
|
||||||
|
RelationshipService rels,
|
||||||
|
IServiceScopeFactory factory
|
||||||
|
)
|
||||||
: ControllerBase
|
: ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -22,8 +29,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
|||||||
var currentUser = currentUserValue as Account.Account;
|
var currentUser = currentUserValue as Account.Account;
|
||||||
var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser);
|
var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser);
|
||||||
|
|
||||||
var publisher = pubName == null ? null :
|
var publisher = pubName == null ? null : await db.Publishers.FirstOrDefaultAsync(p => p.Name == pubName);
|
||||||
await db.Publishers.FirstOrDefaultAsync(p => p.Name == pubName);
|
|
||||||
|
|
||||||
var query = db.Posts.AsQueryable();
|
var query = db.Posts.AsQueryable();
|
||||||
if (publisher != null)
|
if (publisher != null)
|
||||||
@ -150,12 +156,12 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
|||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
Publisher? publisher;
|
Publisher.Publisher? publisher;
|
||||||
if (publisherName is null)
|
if (publisherName is null)
|
||||||
{
|
{
|
||||||
// Use the first personal publisher
|
// Use the first personal publisher
|
||||||
publisher = await db.Publishers.FirstOrDefaultAsync(e =>
|
publisher = await db.Publishers.FirstOrDefaultAsync(e =>
|
||||||
e.AccountId == currentUser.Id && e.PublisherType == PublisherType.Individual);
|
e.AccountId == currentUser.Id && e.Type == PublisherType.Individual);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -281,10 +287,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
|
|
||||||
var member = await db.PublisherMembers
|
if (!await pub.IsMemberWithRole(post.Publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
|
||||||
.FirstOrDefaultAsync(e => e.AccountId == currentUser.Id && e.PublisherId == post.Publisher.Id);
|
|
||||||
if (member is null) return StatusCode(403, "You even wasn't a member of the publisher you specified.");
|
|
||||||
if (member.Role < PublisherMemberRole.Editor)
|
|
||||||
return StatusCode(403, "You need at least be an editor to edit this publisher's post.");
|
return StatusCode(403, "You need at least be an editor to edit this publisher's post.");
|
||||||
|
|
||||||
if (request.Title is not null) post.Title = request.Title;
|
if (request.Title is not null) post.Title = request.Title;
|
||||||
@ -324,14 +327,10 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
|
|
||||||
var member = await db.PublisherMembers
|
if (!await pub.IsMemberWithRole(post.Publisher.Id, currentUser.Id, PublisherMemberRole.Editor))
|
||||||
.FirstOrDefaultAsync(e => e.AccountId == currentUser.Id && e.PublisherId == post.Publisher.Id);
|
|
||||||
if (member is null) return StatusCode(403, "You even wasn't a member of the publisher you specified.");
|
|
||||||
if (member.Role < PublisherMemberRole.Editor)
|
|
||||||
return StatusCode(403, "You need at least be an editor to delete the publisher's post.");
|
return StatusCode(403, "You need at least be an editor to delete the publisher's post.");
|
||||||
|
|
||||||
await ps.DeletePostAsync(post);
|
await ps.DeletePostAsync(post);
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,9 +14,11 @@ using DysonNetwork.Sphere.Connection.Handlers;
|
|||||||
using DysonNetwork.Sphere.Localization;
|
using DysonNetwork.Sphere.Localization;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
|
using DysonNetwork.Sphere.Publisher;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
using DysonNetwork.Sphere.Sticker;
|
using DysonNetwork.Sphere.Sticker;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
|
using DysonNetwork.Sphere.Wallet;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Localization;
|
using Microsoft.AspNetCore.Localization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -165,6 +167,8 @@ builder.Services.AddScoped<RealmService>();
|
|||||||
builder.Services.AddScoped<ChatRoomService>();
|
builder.Services.AddScoped<ChatRoomService>();
|
||||||
builder.Services.AddScoped<ChatService>();
|
builder.Services.AddScoped<ChatService>();
|
||||||
builder.Services.AddScoped<StickerService>();
|
builder.Services.AddScoped<StickerService>();
|
||||||
|
builder.Services.AddScoped<WalletService>();
|
||||||
|
builder.Services.AddScoped<PaymentService>();
|
||||||
|
|
||||||
// Timed task
|
// Timed task
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Post;
|
namespace DysonNetwork.Sphere.Publisher;
|
||||||
|
|
||||||
public enum PublisherType
|
public enum PublisherType
|
||||||
{
|
{
|
||||||
@ -17,7 +17,7 @@ public enum PublisherType
|
|||||||
public class Publisher : ModelBase
|
public class Publisher : ModelBase
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public PublisherType PublisherType { get; set; }
|
public PublisherType Type { get; set; }
|
||||||
[MaxLength(256)] public string Name { get; set; } = string.Empty;
|
[MaxLength(256)] public string Name { get; set; } = string.Empty;
|
||||||
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
|
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
|
||||||
[MaxLength(4096)] public string? Bio { get; set; }
|
[MaxLength(4096)] public string? Bio { get; set; }
|
||||||
@ -27,7 +27,7 @@ public class Publisher : ModelBase
|
|||||||
public string? BackgroundId { get; set; }
|
public string? BackgroundId { get; set; }
|
||||||
public CloudFile? Background { get; set; }
|
public CloudFile? Background { get; set; }
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
|
[JsonIgnore] public ICollection<Post.Post> Posts { get; set; } = new List<Post.Post>();
|
||||||
[JsonIgnore] public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
|
[JsonIgnore] public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
|
||||||
[JsonIgnore] public ICollection<PublisherMember> Members { get; set; } = new List<PublisherMember>();
|
[JsonIgnore] public ICollection<PublisherMember> Members { get; set; } = new List<PublisherMember>();
|
||||||
|
|
||||||
@ -78,3 +78,13 @@ public class PublisherSubscription : ModelBase
|
|||||||
public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Active;
|
public SubscriptionStatus Status { get; set; } = SubscriptionStatus.Active;
|
||||||
public int Tier { get; set; } = 0;
|
public int Tier { get; set; } = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PublisherFeature : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
[MaxLength(1024)] public string Flag { get; set; } = null!;
|
||||||
|
public Instant? ExpiredAt { get; set; }
|
||||||
|
|
||||||
|
public Guid PublisherId { get; set; }
|
||||||
|
public Publisher Publisher { get; set; } = null!;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -7,7 +8,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Post;
|
namespace DysonNetwork.Sphere.Publisher;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/publishers")]
|
[Route("/publishers")]
|
||||||
@ -15,7 +16,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
: ControllerBase
|
: ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("{name}")]
|
[HttpGet("{name}")]
|
||||||
public async Task<ActionResult<Publisher>> GetPublisher(string name)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> GetPublisher(string name)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<List<Publisher>>> ListManagedPublishers()
|
public async Task<ActionResult<List<Sphere.Publisher.Publisher>>> ListManagedPublishers()
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
@ -121,7 +122,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
|
|
||||||
[HttpPost("invites/{name}/accept")]
|
[HttpPost("invites/{name}/accept")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<Publisher>> AcceptMemberInvite(string name)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> AcceptMemberInvite(string name)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
@ -173,7 +174,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
[HttpPost("individual")]
|
[HttpPost("individual")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[RequiredPermission("global", "publishers.create")]
|
[RequiredPermission("global", "publishers.create")]
|
||||||
public async Task<ActionResult<Publisher>> CreatePublisherIndividual([FromBody] PublisherRequest request)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> CreatePublisherIndividual([FromBody] PublisherRequest request)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
@ -217,7 +218,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
[HttpPost("organization/{realmSlug}")]
|
[HttpPost("organization/{realmSlug}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[RequiredPermission("global", "publishers.create")]
|
[RequiredPermission("global", "publishers.create")]
|
||||||
public async Task<ActionResult<Publisher>> CreatePublisherOrganization(string realmSlug, [FromBody] PublisherRequest request)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> CreatePublisherOrganization(string realmSlug, [FromBody] PublisherRequest request)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
@ -265,7 +266,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
|
|
||||||
[HttpPatch("{name}")]
|
[HttpPatch("{name}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<Publisher>> UpdatePublisher(string name, PublisherRequest request)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> UpdatePublisher(string name, PublisherRequest request)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
||||||
@ -316,7 +317,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
|||||||
|
|
||||||
[HttpDelete("{name}")]
|
[HttpDelete("{name}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<Publisher>> DeletePublisher(string name)
|
public async Task<ActionResult<Sphere.Publisher.Publisher>> DeletePublisher(string name)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var userId = currentUser.Id;
|
var userId = currentUser.Id;
|
@ -1,9 +1,10 @@
|
|||||||
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Post;
|
namespace DysonNetwork.Sphere.Publisher;
|
||||||
|
|
||||||
public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache)
|
public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache)
|
||||||
{
|
{
|
||||||
@ -18,7 +19,7 @@ public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache
|
|||||||
{
|
{
|
||||||
var publisher = new Publisher
|
var publisher = new Publisher
|
||||||
{
|
{
|
||||||
PublisherType = PublisherType.Individual,
|
Type = PublisherType.Individual,
|
||||||
Name = name ?? account.Name,
|
Name = name ?? account.Name,
|
||||||
Nick = nick ?? account.Nick,
|
Nick = nick ?? account.Nick,
|
||||||
Bio = bio ?? account.Profile.Bio,
|
Bio = bio ?? account.Profile.Bio,
|
||||||
@ -57,7 +58,7 @@ public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache
|
|||||||
{
|
{
|
||||||
var publisher = new Publisher
|
var publisher = new Publisher
|
||||||
{
|
{
|
||||||
PublisherType = PublisherType.Organizational,
|
Type = PublisherType.Organizational,
|
||||||
Name = name ?? realm.Slug,
|
Name = name ?? realm.Slug,
|
||||||
Nick = nick ?? realm.Name,
|
Nick = nick ?? realm.Name,
|
||||||
Bio = bio ?? realm.Description,
|
Bio = bio ?? realm.Description,
|
||||||
@ -95,6 +96,7 @@ public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const string PublisherStatsCacheKey = "PublisherStats_{0}";
|
private const string PublisherStatsCacheKey = "PublisherStats_{0}";
|
||||||
|
private const string PublisherFeatureCacheKey = "PublisherFeature_{0}_{1}";
|
||||||
|
|
||||||
public async Task<PublisherStats?> GetPublisherStats(string name)
|
public async Task<PublisherStats?> GetPublisherStats(string name)
|
||||||
{
|
{
|
||||||
@ -134,4 +136,56 @@ public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache
|
|||||||
cache.Set(cacheKey, stats, TimeSpan.FromMinutes(5));
|
cache.Set(cacheKey, stats, TimeSpan.FromMinutes(5));
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetFeatureFlag(Guid publisherId, string flag)
|
||||||
|
{
|
||||||
|
var featureFlag = await db.PublisherFeatures
|
||||||
|
.FirstOrDefaultAsync(f => f.PublisherId == publisherId && f.Flag == flag);
|
||||||
|
|
||||||
|
if (featureFlag == null)
|
||||||
|
{
|
||||||
|
featureFlag = new PublisherFeature
|
||||||
|
{
|
||||||
|
PublisherId = publisherId,
|
||||||
|
Flag = flag,
|
||||||
|
};
|
||||||
|
db.PublisherFeatures.Add(featureFlag);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
featureFlag.ExpiredAt = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
cache.Remove(string.Format(PublisherFeatureCacheKey, publisherId, flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HasFeature(Guid publisherId, string flag)
|
||||||
|
{
|
||||||
|
var cacheKey = string.Format(PublisherFeatureCacheKey, publisherId, flag);
|
||||||
|
|
||||||
|
if (cache.TryGetValue(cacheKey, out bool isEnabled))
|
||||||
|
return isEnabled;
|
||||||
|
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var featureFlag = await db.PublisherFeatures
|
||||||
|
.FirstOrDefaultAsync(f =>
|
||||||
|
f.PublisherId == publisherId && f.Flag == flag &&
|
||||||
|
(f.ExpiredAt == null || f.ExpiredAt > now)
|
||||||
|
);
|
||||||
|
if (featureFlag is not null) isEnabled = true;
|
||||||
|
|
||||||
|
cache.Set(cacheKey, isEnabled, TimeSpan.FromMinutes(5));
|
||||||
|
return isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsMemberWithRole(Guid publisherId, Guid accountId, PublisherMemberRole requiredRole)
|
||||||
|
{
|
||||||
|
var member = await db.Publishers
|
||||||
|
.Where(p => p.Id == publisherId)
|
||||||
|
.SelectMany(p => p.Members)
|
||||||
|
.FirstOrDefaultAsync(m => m.AccountId == accountId);
|
||||||
|
|
||||||
|
return member != null && member.Role >= requiredRole;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
|
using DysonNetwork.Sphere.Post;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Post;
|
namespace DysonNetwork.Sphere.Publisher;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/publishers")]
|
[Route("/publishers")]
|
@ -1,8 +1,7 @@
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Post;
|
namespace DysonNetwork.Sphere.Publisher;
|
||||||
|
|
||||||
public class PublisherSubscriptionService(AppDatabase db, NotificationService nty)
|
public class PublisherSubscriptionService(AppDatabase db, NotificationService nty)
|
||||||
{
|
{
|
||||||
@ -38,7 +37,7 @@ public class PublisherSubscriptionService(AppDatabase db, NotificationService nt
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="post">The new post</param>
|
/// <param name="post">The new post</param>
|
||||||
/// <returns>The number of subscribers notified</returns>
|
/// <returns>The number of subscribers notified</returns>
|
||||||
public async Task<int> NotifySubscribersPostAsync(Post post)
|
public async Task<int> NotifySubscribersPostAsync(Post.Post post)
|
||||||
{
|
{
|
||||||
var subscribers = await db.PublisherSubscriptions
|
var subscribers = await db.PublisherSubscriptions
|
||||||
.Include(ps => ps.Account)
|
.Include(ps => ps.Account)
|
@ -7,7 +7,7 @@ namespace DysonNetwork.Sphere.Realm;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/realm/{slug}")]
|
[Route("/realm/{slug}")]
|
||||||
public class RealmChatController(AppDatabase db) : ControllerBase
|
public class RealmChatController(AppDatabase db, RealmService rs) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("chat")]
|
[HttpGet("chat")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@ -22,11 +22,8 @@ public class RealmChatController(AppDatabase db) : ControllerBase
|
|||||||
if (!realm.IsPublic)
|
if (!realm.IsPublic)
|
||||||
{
|
{
|
||||||
if (currentUser is null) return Unauthorized();
|
if (currentUser is null) return Unauthorized();
|
||||||
var member = await db.ChatMembers
|
if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Normal))
|
||||||
.Where(m => m.ChatRoomId == realm.Id)
|
return StatusCode(403, "You need at least one member to view the realm's chat.");
|
||||||
.Where(m => m.AccountId == currentUser.Id)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (member is null) return BadRequest("You need at least one member to view the realm's chat.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var chatRooms = await db.ChatRooms
|
var chatRooms = await db.ChatRooms
|
||||||
|
@ -78,15 +78,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs) :
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (realm is null) return NotFound();
|
if (realm is null) return NotFound();
|
||||||
|
|
||||||
var member = await db.RealmMembers
|
if (!await rs.IsMemberWithRole(realm.Id, userId, request.Role))
|
||||||
.Where(m => m.AccountId == userId)
|
|
||||||
.Where(m => m.RealmId == realm.Id)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (member is null) return StatusCode(403, "You are not even a member of the targeted realm.");
|
|
||||||
if (member.Role < RealmMemberRole.Moderator)
|
|
||||||
return StatusCode(403,
|
|
||||||
"You need at least be a manager to invite other members to collaborate this realm.");
|
|
||||||
if (member.Role < request.Role)
|
|
||||||
return StatusCode(403, "You cannot invite member has higher permission than yours.");
|
return StatusCode(403, "You cannot invite member has higher permission than yours.");
|
||||||
|
|
||||||
var newMember = new RealmMember
|
var newMember = new RealmMember
|
||||||
@ -162,9 +154,8 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs) :
|
|||||||
if (!realm.IsPublic)
|
if (!realm.IsPublic)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
var isMember = await db.RealmMembers
|
if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Normal))
|
||||||
.AnyAsync(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id && m.JoinedAt != null);
|
return StatusCode(403, "You must be a member to view this realm's members.");
|
||||||
if (!isMember) return StatusCode(403, "You must be a member to view this realm's members.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = db.RealmMembers
|
var query = db.RealmMembers
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Realm;
|
namespace DysonNetwork.Sphere.Realm;
|
||||||
|
|
||||||
@ -9,4 +10,11 @@ public class RealmService(AppDatabase db, NotificationService nty)
|
|||||||
await nty.SendNotification(member.Account, "invites.realms", "New Realm Invitation", null,
|
await nty.SendNotification(member.Account, "invites.realms", "New Realm Invitation", null,
|
||||||
$"You just got invited to join {member.Realm.Name}");
|
$"You just got invited to join {member.Realm.Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsMemberWithRole(Guid realmId, Guid accountId, RealmMemberRole requiredRole)
|
||||||
|
{
|
||||||
|
var member = await db.RealmMembers
|
||||||
|
.FirstOrDefaultAsync(m => m.RealmId == realmId && m.AccountId == accountId);
|
||||||
|
return member?.Role >= requiredRole;
|
||||||
|
}
|
||||||
}
|
}
|
@ -23,6 +23,6 @@ public class StickerPack : ModelBase
|
|||||||
[MaxLength(128)] public string Prefix { get; set; } = null!;
|
[MaxLength(128)] public string Prefix { get; set; } = null!;
|
||||||
|
|
||||||
public Guid PublisherId { get; set; }
|
public Guid PublisherId { get; set; }
|
||||||
public Post.Publisher Publisher { get; set; } = null!;
|
public Publisher.Publisher Publisher { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
|
using DysonNetwork.Sphere.Publisher;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
51
DysonNetwork.Sphere/Wallet/Payment.cs
Normal file
51
DysonNetwork.Sphere/Wallet/Payment.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
|
|
||||||
|
public enum OrderStatus
|
||||||
|
{
|
||||||
|
Unpaid,
|
||||||
|
Paid,
|
||||||
|
Cancelled,
|
||||||
|
Finished,
|
||||||
|
Expired
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Order : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public OrderStatus Status { get; set; } = OrderStatus.Unpaid;
|
||||||
|
[MaxLength(128)] public string Currency { get; set; } = null!;
|
||||||
|
[MaxLength(4096)] public string? Remarks { get; set; }
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
public Instant ExpiredAt { get; set; }
|
||||||
|
|
||||||
|
public Guid PayeeWalletId { get; set; }
|
||||||
|
public Wallet PayeeWallet { get; set; } = null!;
|
||||||
|
public Guid? TransactionId { get; set; }
|
||||||
|
public Transaction? Transaction { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TransactionType
|
||||||
|
{
|
||||||
|
System,
|
||||||
|
Transfer,
|
||||||
|
Order
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Transaction : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(128)] public string Currency { get; set; } = null!;
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
[MaxLength(4096)] public string? Remarks { get; set; }
|
||||||
|
public TransactionType Type { get; set; }
|
||||||
|
|
||||||
|
// When the payer is null, it's pay from the system
|
||||||
|
public Guid? PayerWalletId { get; set; }
|
||||||
|
public Wallet? PayerWallet { get; set; }
|
||||||
|
// When the payee is null, it's pay for the system
|
||||||
|
public Guid? PayeeWalletId { get; set; }
|
||||||
|
public Wallet? PayeeWallet { get; set; }
|
||||||
|
}
|
184
DysonNetwork.Sphere/Wallet/PaymentService.cs
Normal file
184
DysonNetwork.Sphere/Wallet/PaymentService.cs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
|
|
||||||
|
public class PaymentService(AppDatabase db, WalletService wat)
|
||||||
|
{
|
||||||
|
public async Task<Order> CreateOrderAsync(Guid payeeWalletId, string currency, decimal amount, Duration expiration)
|
||||||
|
{
|
||||||
|
var order = new Order
|
||||||
|
{
|
||||||
|
PayeeWalletId = payeeWalletId,
|
||||||
|
Currency = currency,
|
||||||
|
Amount = amount,
|
||||||
|
ExpiredAt = SystemClock.Instance.GetCurrentInstant().Plus(expiration)
|
||||||
|
};
|
||||||
|
|
||||||
|
db.PaymentOrders.Add(order);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Transaction> CreateTransactionAsync(
|
||||||
|
Guid? payerWalletId,
|
||||||
|
Guid? payeeWalletId,
|
||||||
|
string currency,
|
||||||
|
decimal amount,
|
||||||
|
string? remarks = null,
|
||||||
|
TransactionType type = TransactionType.System
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var transaction = new Transaction
|
||||||
|
{
|
||||||
|
PayerWalletId = payerWalletId,
|
||||||
|
PayeeWalletId = payeeWalletId,
|
||||||
|
Currency = currency,
|
||||||
|
Amount = amount,
|
||||||
|
Remarks = remarks,
|
||||||
|
Type = type
|
||||||
|
};
|
||||||
|
|
||||||
|
if (payerWalletId.HasValue)
|
||||||
|
{
|
||||||
|
var payerPocket = await wat.GetOrCreateWalletPocketAsync(
|
||||||
|
(await db.Wallets.FindAsync(payerWalletId.Value))!.AccountId,
|
||||||
|
currency);
|
||||||
|
|
||||||
|
if (payerPocket.Amount < amount)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Insufficient funds");
|
||||||
|
}
|
||||||
|
|
||||||
|
payerPocket.Amount -= amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payeeWalletId.HasValue)
|
||||||
|
{
|
||||||
|
var payeeWallet = await db.Wallets.FindAsync(payeeWalletId.Value);
|
||||||
|
var payeePocket = await wat.GetOrCreateWalletPocketAsync(
|
||||||
|
payeeWallet!.AccountId,
|
||||||
|
currency);
|
||||||
|
|
||||||
|
payeePocket.Amount += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.PaymentTransactions.Add(transaction);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Order> PayOrderAsync(Guid orderId, Guid payerWalletId)
|
||||||
|
{
|
||||||
|
var order = await db.PaymentOrders
|
||||||
|
.Include(o => o.Transaction)
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == orderId);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Order not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.Status != OrderStatus.Unpaid)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Order is in invalid status: {order.Status}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.ExpiredAt < SystemClock.Instance.GetCurrentInstant())
|
||||||
|
{
|
||||||
|
order.Status = OrderStatus.Expired;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
throw new InvalidOperationException("Order has expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
var transaction = await CreateTransactionAsync(
|
||||||
|
payerWalletId,
|
||||||
|
order.PayeeWalletId,
|
||||||
|
order.Currency,
|
||||||
|
order.Amount,
|
||||||
|
order.Remarks ?? $"Payment for Order #{order.Id}",
|
||||||
|
type: TransactionType.Order);
|
||||||
|
|
||||||
|
order.TransactionId = transaction.Id;
|
||||||
|
order.Transaction = transaction;
|
||||||
|
order.Status = OrderStatus.Paid;
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Order> CancelOrderAsync(Guid orderId)
|
||||||
|
{
|
||||||
|
var order = await db.PaymentOrders.FindAsync(orderId);
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Order not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.Status != OrderStatus.Unpaid)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cannot cancel order in status: {order.Status}");
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Status = OrderStatus.Cancelled;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(Order Order, Transaction RefundTransaction)> RefundOrderAsync(Guid orderId)
|
||||||
|
{
|
||||||
|
var order = await db.PaymentOrders
|
||||||
|
.Include(o => o.Transaction)
|
||||||
|
.FirstOrDefaultAsync(o => o.Id == orderId);
|
||||||
|
|
||||||
|
if (order == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Order not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.Status != OrderStatus.Paid)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cannot refund order in status: {order.Status}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.Transaction == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Order has no associated transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
var refundTransaction = await CreateTransactionAsync(
|
||||||
|
order.PayeeWalletId,
|
||||||
|
order.Transaction.PayerWalletId,
|
||||||
|
order.Currency,
|
||||||
|
order.Amount,
|
||||||
|
$"Refund for order {order.Id}");
|
||||||
|
|
||||||
|
order.Status = OrderStatus.Finished;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return (order, refundTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Transaction> TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency, decimal amount)
|
||||||
|
{
|
||||||
|
var payerWallet = await wat.GetWalletAsync(payerAccountId);
|
||||||
|
if (payerWallet == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Payer wallet not found for account {payerAccountId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var payeeWallet = await wat.GetWalletAsync(payeeAccountId);
|
||||||
|
if (payeeWallet == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Payee wallet not found for account {payeeAccountId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return await CreateTransactionAsync(
|
||||||
|
payerWallet.Id,
|
||||||
|
payeeWallet.Id,
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
$"Transfer from account {payerAccountId} to {payeeAccountId}",
|
||||||
|
TransactionType.Transfer);
|
||||||
|
}
|
||||||
|
}
|
23
DysonNetwork.Sphere/Wallet/Wallet.cs
Normal file
23
DysonNetwork.Sphere/Wallet/Wallet.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
|
|
||||||
|
public class Wallet : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public ICollection<WalletPocket> Pockets { get; set; } = new List<WalletPocket>();
|
||||||
|
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
public Account.Account Account { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WalletPocket : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
[MaxLength(128)] public string Currency { get; set; } = null!;
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
public Guid WalletId { get; set; }
|
||||||
|
public Wallet Wallet { get; set; } = null!;
|
||||||
|
}
|
54
DysonNetwork.Sphere/Wallet/WalletService.cs
Normal file
54
DysonNetwork.Sphere/Wallet/WalletService.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
|
|
||||||
|
public class WalletService(AppDatabase db)
|
||||||
|
{
|
||||||
|
public async Task<Wallet?> GetWalletAsync(Guid accountId)
|
||||||
|
{
|
||||||
|
return await db.Wallets
|
||||||
|
.Include(w => w.Pockets)
|
||||||
|
.FirstOrDefaultAsync(w => w.AccountId == accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Wallet> CreateWalletAsync(Guid accountId)
|
||||||
|
{
|
||||||
|
var wallet = new Wallet
|
||||||
|
{
|
||||||
|
AccountId = accountId
|
||||||
|
};
|
||||||
|
|
||||||
|
db.Wallets.Add(wallet);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WalletPocket> GetOrCreateWalletPocketAsync(Guid accountId, string currency)
|
||||||
|
{
|
||||||
|
var wallet = await db.Wallets
|
||||||
|
.Include(w => w.Pockets)
|
||||||
|
.FirstOrDefaultAsync(w => w.AccountId == accountId);
|
||||||
|
|
||||||
|
if (wallet == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Wallet not found for account {accountId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var pocket = wallet.Pockets.FirstOrDefault(p => p.Currency == currency);
|
||||||
|
|
||||||
|
if (pocket != null) return pocket;
|
||||||
|
|
||||||
|
pocket = new WalletPocket
|
||||||
|
{
|
||||||
|
Currency = currency,
|
||||||
|
Amount = 0,
|
||||||
|
WalletId = wallet.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
wallet.Pockets.Add(pocket);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return pocket;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user