♻️ Refactored order handling
This commit is contained in:
@@ -14,7 +14,7 @@ public class BroadcastEventHandler(
|
|||||||
{
|
{
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
await foreach (var msg in nats.SubscribeAsync<byte[]>("accounts.deleted", cancellationToken: stoppingToken))
|
await foreach (var msg in nats.SubscribeAsync<byte[]>(AccountDeletedEvent.Type, cancellationToken: stoppingToken))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
2021
DysonNetwork.Pass/Migrations/20250904144723_AddOrderProductIdentifier.Designer.cs
generated
Normal file
2021
DysonNetwork.Pass/Migrations/20250904144723_AddOrderProductIdentifier.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddOrderProductIdentifier : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "product_identifier",
|
||||||
|
table: "payment_orders",
|
||||||
|
type: "character varying(4096)",
|
||||||
|
maxLength: 4096,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "product_identifier",
|
||||||
|
table: "payment_orders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1381,6 +1381,11 @@ namespace DysonNetwork.Pass.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("payee_wallet_id");
|
.HasColumnName("payee_wallet_id");
|
||||||
|
|
||||||
|
b.Property<string>("ProductIdentifier")
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("product_identifier");
|
||||||
|
|
||||||
b.Property<string>("Remarks")
|
b.Property<string>("Remarks")
|
||||||
.HasMaxLength(4096)
|
.HasMaxLength(4096)
|
||||||
.HasColumnType("character varying(4096)")
|
.HasColumnType("character varying(4096)")
|
||||||
|
@@ -170,5 +170,47 @@ namespace DysonNetwork.Sphere.Resources.Localization {
|
|||||||
return ResourceManager.GetString("NewLoginBody", resourceCulture);
|
return ResourceManager.GetString("NewLoginBody", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string FriendRequestTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FriendRequestTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string FriendRequestBody {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("FriendRequestBody", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string OrderReceivedTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OrderReceivedTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string OrderReceivedBody {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OrderReceivedBody", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string TransactionNewTitle {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TransactionNewTitle", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string TransactionNewBodyPlus {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TransactionNewBodyPlus", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string TransactionNewBodyMinus {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TransactionNewBodyMinus", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -78,7 +78,7 @@
|
|||||||
<value>Order {0} recipent</value>
|
<value>Order {0} recipent</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="OrderPaidBody" xml:space="preserve">
|
<data name="OrderPaidBody" xml:space="preserve">
|
||||||
<value>{0} {1} was removed from your wallet to pay {2}</value>
|
<value>Paid order {2} with {0} {1}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NewLoginTitle" xml:space="preserve">
|
<data name="NewLoginTitle" xml:space="preserve">
|
||||||
<value>New login detected</value>
|
<value>New login detected</value>
|
||||||
@@ -92,4 +92,19 @@
|
|||||||
<data name="FriendRequestBody" xml:space="preserve">
|
<data name="FriendRequestBody" xml:space="preserve">
|
||||||
<value>You can go to relationships page and decide accept their request or not.</value>
|
<value>You can go to relationships page and decide accept their request or not.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="OrderReceivedTitle" xml:space="preserve">
|
||||||
|
<value>Order {0} recipent</value>
|
||||||
|
</data>
|
||||||
|
<data name="OrderReceivedBody" xml:space="preserve">
|
||||||
|
<value>Received {2} payment of {0} {1}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TransactionNewTitle" xml:space="preserve">
|
||||||
|
<value>Transaction {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TransactionNewBodyPlus" xml:space="preserve">
|
||||||
|
<value>{0} {1} added to your wallet</value>
|
||||||
|
</data>
|
||||||
|
<data name="TransactionNewBodyMinus" xml:space="preserve">
|
||||||
|
<value>{0} {1} removed from your wallet</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@@ -67,10 +67,10 @@
|
|||||||
<value>感谢你支持 Solar Network 的开发!你的 {0} 天 {1} 订阅刚刚开始,接下来来探索新解锁的新功能吧!</value>
|
<value>感谢你支持 Solar Network 的开发!你的 {0} 天 {1} 订阅刚刚开始,接下来来探索新解锁的新功能吧!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="OrderPaidTitle" xml:space="preserve">
|
<data name="OrderPaidTitle" xml:space="preserve">
|
||||||
<value>订单回执 {0}</value>
|
<value>订单收据 {0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="OrderPaidBody" xml:space="preserve">
|
<data name="OrderPaidBody" xml:space="preserve">
|
||||||
<value>{0} {1} 已从你的帐户中扣除来支付 {2}</value>
|
<value>已支付订单 {2} 的 {0} {1}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NewLoginTitle" xml:space="preserve">
|
<data name="NewLoginTitle" xml:space="preserve">
|
||||||
<value>检测到新登陆</value>
|
<value>检测到新登陆</value>
|
||||||
@@ -84,4 +84,19 @@
|
|||||||
<data name="FriendRequestBody" xml:space="preserve">
|
<data name="FriendRequestBody" xml:space="preserve">
|
||||||
<value>您可以前往人际关系页面来决定时候要接受他们的邀请。</value>
|
<value>您可以前往人际关系页面来决定时候要接受他们的邀请。</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="OrderReceivedTitle" xml:space="preserve">
|
||||||
|
<value>订单收据 {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="OrderReceivedBody" xml:space="preserve">
|
||||||
|
<value>收到订单 {2} 支付的 {0} {1}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TransactionNewTitle" xml:space="preserve">
|
||||||
|
<value>交易 {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="TransactionNewBodyPlus" xml:space="preserve">
|
||||||
|
<value>{0} {1} 添加到了您的钱包</value>
|
||||||
|
</data>
|
||||||
|
<data name="TransactionNewBodyMinus" xml:space="preserve">
|
||||||
|
<value>{0} {1} 从您的钱包移除</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@@ -4,6 +4,7 @@ using DysonNetwork.Pass.Auth;
|
|||||||
using DysonNetwork.Pass.Credit;
|
using DysonNetwork.Pass.Credit;
|
||||||
using DysonNetwork.Pass.Leveling;
|
using DysonNetwork.Pass.Leveling;
|
||||||
using DysonNetwork.Pass.Permission;
|
using DysonNetwork.Pass.Permission;
|
||||||
|
using DysonNetwork.Pass.Wallet;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
@@ -81,6 +82,8 @@ public static class ApplicationConfiguration
|
|||||||
app.MapGrpcService<SocialCreditServiceGrpc>();
|
app.MapGrpcService<SocialCreditServiceGrpc>();
|
||||||
app.MapGrpcService<ExperienceServiceGrpc>();
|
app.MapGrpcService<ExperienceServiceGrpc>();
|
||||||
app.MapGrpcService<BotAccountReceiverGrpc>();
|
app.MapGrpcService<BotAccountReceiverGrpc>();
|
||||||
|
app.MapGrpcService<WalletServiceGrpc>();
|
||||||
|
app.MapGrpcService<PaymentServiceGrpc>();
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
34
DysonNetwork.Pass/Startup/BroadcastEventHandler.cs
Normal file
34
DysonNetwork.Pass/Startup/BroadcastEventHandler.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using DysonNetwork.Pass.Wallet;
|
||||||
|
using DysonNetwork.Shared.Stream;
|
||||||
|
using NATS.Client.Core;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Startup;
|
||||||
|
|
||||||
|
public class BroadcastEventHandler(
|
||||||
|
INatsConnection nats,
|
||||||
|
ILogger<BroadcastEventHandler> logger,
|
||||||
|
IServiceProvider serviceProvider
|
||||||
|
) : BackgroundService
|
||||||
|
{
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
await foreach (var msg in nats.SubscribeAsync<byte[]>(PaymentOrderEvent.Type, cancellationToken: stoppingToken))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var evt = JsonSerializer.Deserialize<PaymentOrderEvent>(msg.Data);
|
||||||
|
|
||||||
|
if (evt?.ProductIdentifier is null || !evt.ProductIdentifier.StartsWith(SubscriptionType.StellarProgram))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
logger.LogInformation("Stellar program order paid: {OrderId}", evt.OrderId);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error processing AccountDeleted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -54,10 +54,6 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
services.AddPusherService();
|
services.AddPusherService();
|
||||||
|
|
||||||
// Register gRPC services
|
|
||||||
services.AddScoped<AccountServiceGrpc>();
|
|
||||||
services.AddScoped<AuthServiceGrpc>();
|
|
||||||
|
|
||||||
// Register OIDC services
|
// Register OIDC services
|
||||||
services.AddScoped<OidcService, GoogleOidcService>();
|
services.AddScoped<OidcService, GoogleOidcService>();
|
||||||
services.AddScoped<OidcService, AppleOidcService>();
|
services.AddScoped<OidcService, AppleOidcService>();
|
||||||
|
@@ -15,9 +15,7 @@ public class OrderController(PaymentService payment, AuthService auth, AppDataba
|
|||||||
var order = await db.PaymentOrders.FindAsync(id);
|
var order = await db.PaymentOrders.FindAsync(id);
|
||||||
|
|
||||||
if (order == null)
|
if (order == null)
|
||||||
{
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(order);
|
return Ok(order);
|
||||||
}
|
}
|
||||||
@@ -41,7 +39,7 @@ public class OrderController(PaymentService payment, AuthService auth, AppDataba
|
|||||||
return BadRequest("Wallet was not found.");
|
return BadRequest("Wallet was not found.");
|
||||||
|
|
||||||
// Pay the order
|
// Pay the order
|
||||||
var paidOrder = await payment.PayOrderAsync(id, wallet.Id);
|
var paidOrder = await payment.PayOrderAsync(id, wallet);
|
||||||
return Ok(paidOrder);
|
return Ok(paidOrder);
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using System.Globalization;
|
||||||
using DysonNetwork.Shared.Data;
|
using DysonNetwork.Shared.Data;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NodaTime.Serialization.Protobuf;
|
using NodaTime.Serialization.Protobuf;
|
||||||
@@ -23,11 +24,14 @@ public enum OrderStatus
|
|||||||
|
|
||||||
public class Order : ModelBase
|
public class Order : ModelBase
|
||||||
{
|
{
|
||||||
|
public const string InternalAppIdentifier = "internal";
|
||||||
|
|
||||||
public Guid Id { get; set; } = Guid.NewGuid();
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
public OrderStatus Status { get; set; } = OrderStatus.Unpaid;
|
public OrderStatus Status { get; set; } = OrderStatus.Unpaid;
|
||||||
[MaxLength(128)] public string Currency { get; set; } = null!;
|
[MaxLength(128)] public string Currency { get; set; } = null!;
|
||||||
[MaxLength(4096)] public string? Remarks { get; set; }
|
[MaxLength(4096)] public string? Remarks { get; set; }
|
||||||
[MaxLength(4096)] public string? AppIdentifier { get; set; }
|
[MaxLength(4096)] public string? AppIdentifier { get; set; }
|
||||||
|
[MaxLength(4096)] public string? ProductIdentifier { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public Instant ExpiredAt { get; set; }
|
public Instant ExpiredAt { get; set; }
|
||||||
@@ -44,10 +48,11 @@ public class Order : ModelBase
|
|||||||
Currency = Currency,
|
Currency = Currency,
|
||||||
Remarks = Remarks,
|
Remarks = Remarks,
|
||||||
AppIdentifier = AppIdentifier,
|
AppIdentifier = AppIdentifier,
|
||||||
|
ProductIdentifier = ProductIdentifier,
|
||||||
Meta = Meta == null
|
Meta = Meta == null
|
||||||
? null
|
? null
|
||||||
: Google.Protobuf.ByteString.CopyFrom(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(Meta)),
|
: Google.Protobuf.ByteString.CopyFrom(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(Meta)),
|
||||||
Amount = Amount.ToString(),
|
Amount = Amount.ToString(CultureInfo.InvariantCulture),
|
||||||
ExpiredAt = ExpiredAt.ToTimestamp(),
|
ExpiredAt = ExpiredAt.ToTimestamp(),
|
||||||
PayeeWalletId = PayeeWalletId?.ToString(),
|
PayeeWalletId = PayeeWalletId?.ToString(),
|
||||||
TransactionId = TransactionId?.ToString(),
|
TransactionId = TransactionId?.ToString(),
|
||||||
@@ -61,6 +66,7 @@ public class Order : ModelBase
|
|||||||
Currency = proto.Currency,
|
Currency = proto.Currency,
|
||||||
Remarks = proto.Remarks,
|
Remarks = proto.Remarks,
|
||||||
AppIdentifier = proto.AppIdentifier,
|
AppIdentifier = proto.AppIdentifier,
|
||||||
|
ProductIdentifier = proto.ProductIdentifier,
|
||||||
Meta = proto.HasMeta
|
Meta = proto.HasMeta
|
||||||
? System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(proto.Meta.ToByteArray())
|
? System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(proto.Meta.ToByteArray())
|
||||||
: null,
|
: null,
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
using DysonNetwork.Pass.Localization;
|
using DysonNetwork.Pass.Localization;
|
||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using DysonNetwork.Shared.Stream;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
using NATS.Client.Core;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using AccountService = DysonNetwork.Pass.Account.AccountService;
|
using AccountService = DysonNetwork.Pass.Account.AccountService;
|
||||||
|
|
||||||
@@ -13,7 +16,8 @@ public class PaymentService(
|
|||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
WalletService wat,
|
WalletService wat,
|
||||||
PusherService.PusherServiceClient pusher,
|
PusherService.PusherServiceClient pusher,
|
||||||
IStringLocalizer<NotificationResource> localizer
|
IStringLocalizer<NotificationResource> localizer,
|
||||||
|
INatsConnection nats
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
public async Task<Order> CreateOrderAsync(
|
public async Task<Order> CreateOrderAsync(
|
||||||
@@ -22,6 +26,7 @@ public class PaymentService(
|
|||||||
decimal amount,
|
decimal amount,
|
||||||
Duration? expiration = null,
|
Duration? expiration = null,
|
||||||
string? appIdentifier = null,
|
string? appIdentifier = null,
|
||||||
|
string? productIdentifier = null,
|
||||||
Dictionary<string, object>? meta = null,
|
Dictionary<string, object>? meta = null,
|
||||||
bool reuseable = true
|
bool reuseable = true
|
||||||
)
|
)
|
||||||
@@ -29,26 +34,25 @@ public class PaymentService(
|
|||||||
// Check if there's an existing unpaid order that can be reused
|
// Check if there's an existing unpaid order that can be reused
|
||||||
if (reuseable && appIdentifier != null)
|
if (reuseable && appIdentifier != null)
|
||||||
{
|
{
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
var existingOrder = await db.PaymentOrders
|
var existingOrder = await db.PaymentOrders
|
||||||
.Where(o => o.Status == OrderStatus.Unpaid &&
|
.Where(o => o.Status == OrderStatus.Unpaid &&
|
||||||
o.PayeeWalletId == payeeWalletId &&
|
o.PayeeWalletId == payeeWalletId &&
|
||||||
o.Currency == currency &&
|
o.Currency == currency &&
|
||||||
o.Amount == amount &&
|
o.Amount == amount &&
|
||||||
o.AppIdentifier == appIdentifier &&
|
o.AppIdentifier == appIdentifier &&
|
||||||
o.ExpiredAt > SystemClock.Instance.GetCurrentInstant())
|
o.ProductIdentifier == productIdentifier &&
|
||||||
|
o.ExpiredAt > now)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
// If an existing order is found, check if meta matches
|
// If an existing order is found, check if meta matches
|
||||||
if (existingOrder != null && meta != null && existingOrder.Meta != null)
|
if (existingOrder != null && meta != null && existingOrder.Meta != null)
|
||||||
{
|
{
|
||||||
// Compare meta dictionaries - if they are equivalent, reuse the order
|
// Compare the meta dictionary - if they are equivalent, reuse the order
|
||||||
var metaMatches = existingOrder.Meta.Count == meta.Count &&
|
var metaMatches = existingOrder.Meta.Count == meta.Count &&
|
||||||
!existingOrder.Meta.Except(meta).Any();
|
!existingOrder.Meta.Except(meta).Any();
|
||||||
|
|
||||||
if (metaMatches)
|
if (metaMatches)
|
||||||
{
|
|
||||||
return existingOrder;
|
return existingOrder;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +64,7 @@ public class PaymentService(
|
|||||||
Amount = amount,
|
Amount = amount,
|
||||||
ExpiredAt = SystemClock.Instance.GetCurrentInstant().Plus(expiration ?? Duration.FromHours(24)),
|
ExpiredAt = SystemClock.Instance.GetCurrentInstant().Plus(expiration ?? Duration.FromHours(24)),
|
||||||
AppIdentifier = appIdentifier,
|
AppIdentifier = appIdentifier,
|
||||||
|
ProductIdentifier = productIdentifier,
|
||||||
Meta = meta
|
Meta = meta
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,7 +109,8 @@ public class PaymentService(
|
|||||||
string currency,
|
string currency,
|
||||||
decimal amount,
|
decimal amount,
|
||||||
string? remarks = null,
|
string? remarks = null,
|
||||||
TransactionType type = TransactionType.System
|
TransactionType type = TransactionType.System,
|
||||||
|
bool silent = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (payerWalletId == null && payeeWalletId == null)
|
if (payerWalletId == null && payeeWalletId == null)
|
||||||
@@ -121,8 +127,12 @@ public class PaymentService(
|
|||||||
Type = type
|
Type = type
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet? payerWallet = null, payeeWallet = null;
|
||||||
|
|
||||||
if (payerWalletId.HasValue)
|
if (payerWalletId.HasValue)
|
||||||
{
|
{
|
||||||
|
payerWallet = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payerWalletId.Value);
|
||||||
|
|
||||||
var (payerPocket, isNewlyCreated) =
|
var (payerPocket, isNewlyCreated) =
|
||||||
await wat.GetOrCreateWalletPocketAsync(payerWalletId.Value, currency);
|
await wat.GetOrCreateWalletPocketAsync(payerWalletId.Value, currency);
|
||||||
|
|
||||||
@@ -137,6 +147,8 @@ public class PaymentService(
|
|||||||
|
|
||||||
if (payeeWalletId.HasValue)
|
if (payeeWalletId.HasValue)
|
||||||
{
|
{
|
||||||
|
payeeWallet = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payeeWalletId.Value);
|
||||||
|
|
||||||
var (payeePocket, isNewlyCreated) =
|
var (payeePocket, isNewlyCreated) =
|
||||||
await wat.GetOrCreateWalletPocketAsync(payeeWalletId.Value, currency, amount);
|
await wat.GetOrCreateWalletPocketAsync(payeeWalletId.Value, currency, amount);
|
||||||
|
|
||||||
@@ -149,13 +161,85 @@ public class PaymentService(
|
|||||||
|
|
||||||
db.PaymentTransactions.Add(transaction);
|
db.PaymentTransactions.Add(transaction);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!silent)
|
||||||
|
await NotifyNewTransaction(transaction, payerWallet, payeeWallet);
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task NotifyNewTransaction(Transaction transaction, Wallet? payerWallet, Wallet? payeeWallet)
|
||||||
|
{
|
||||||
|
if (payerWallet is not null)
|
||||||
|
{
|
||||||
|
var account = await db.Accounts
|
||||||
|
.Where(a => a.Id == payerWallet.AccountId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (account is null) return;
|
||||||
|
|
||||||
public async Task<Order> PayOrderAsync(Guid orderId, Guid payerWalletId)
|
AccountService.SetCultureInfo(account);
|
||||||
|
|
||||||
|
// Due to ID is uuid, it longer than 8 words for sure
|
||||||
|
var readableTransactionId = transaction.Id.ToString().Replace("-", "")[..8];
|
||||||
|
var readableTransactionRemark = transaction.Remarks ?? $"#{readableTransactionId}";
|
||||||
|
|
||||||
|
await pusher.SendPushNotificationToUserAsync(
|
||||||
|
new SendPushNotificationToUserRequest
|
||||||
|
{
|
||||||
|
UserId = account.Id.ToString(),
|
||||||
|
Notification = new PushNotification
|
||||||
|
{
|
||||||
|
Topic = "wallets.transactions",
|
||||||
|
Title = transaction.Amount > 0
|
||||||
|
? localizer["TransactionNewBodyMinus", readableTransactionRemark]
|
||||||
|
: localizer["TransactionNewBodyPlus", readableTransactionRemark],
|
||||||
|
Body = localizer["TransactionNewTitle",
|
||||||
|
transaction.Amount.ToString(CultureInfo.InvariantCulture),
|
||||||
|
transaction.Currency],
|
||||||
|
IsSavable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payeeWallet is not null)
|
||||||
|
{
|
||||||
|
var account = await db.Accounts
|
||||||
|
.Where(a => a.Id == payeeWallet.AccountId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (account is null) return;
|
||||||
|
|
||||||
|
AccountService.SetCultureInfo(account);
|
||||||
|
|
||||||
|
// Due to ID is uuid, it longer than 8 words for sure
|
||||||
|
var readableTransactionId = transaction.Id.ToString().Replace("-", "")[..8];
|
||||||
|
var readableTransactionRemark = transaction.Remarks ?? $"#{readableTransactionId}";
|
||||||
|
|
||||||
|
await pusher.SendPushNotificationToUserAsync(
|
||||||
|
new SendPushNotificationToUserRequest
|
||||||
|
{
|
||||||
|
UserId = account.Id.ToString(),
|
||||||
|
Notification = new PushNotification
|
||||||
|
{
|
||||||
|
Topic = "wallets.transactions",
|
||||||
|
Title = transaction.Amount > 0
|
||||||
|
? localizer["TransactionNewBodyPlus", readableTransactionRemark]
|
||||||
|
: localizer["TransactionNewBodyMinus", readableTransactionRemark],
|
||||||
|
Body = localizer["TransactionNewTitle",
|
||||||
|
transaction.Amount.ToString(CultureInfo.InvariantCulture),
|
||||||
|
transaction.Currency],
|
||||||
|
IsSavable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Order> PayOrderAsync(Guid orderId, Wallet payerWallet)
|
||||||
{
|
{
|
||||||
var order = await db.PaymentOrders
|
var order = await db.PaymentOrders
|
||||||
.Include(o => o.Transaction)
|
.Include(o => o.Transaction)
|
||||||
|
.Include(o => o.PayeeWallet)
|
||||||
.FirstOrDefaultAsync(o => o.Id == orderId);
|
.FirstOrDefaultAsync(o => o.Id == orderId);
|
||||||
|
|
||||||
if (order == null)
|
if (order == null)
|
||||||
@@ -176,7 +260,7 @@ public class PaymentService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var transaction = await CreateTransactionAsync(
|
var transaction = await CreateTransactionAsync(
|
||||||
payerWalletId,
|
payerWallet.Id,
|
||||||
order.PayeeWalletId,
|
order.PayeeWalletId,
|
||||||
order.Currency,
|
order.Currency,
|
||||||
order.Amount,
|
order.Amount,
|
||||||
@@ -186,41 +270,87 @@ public class PaymentService(
|
|||||||
order.TransactionId = transaction.Id;
|
order.TransactionId = transaction.Id;
|
||||||
order.Transaction = transaction;
|
order.Transaction = transaction;
|
||||||
order.Status = OrderStatus.Paid;
|
order.Status = OrderStatus.Paid;
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
await NotifyOrderPaid(order);
|
await NotifyOrderPaid(order, payerWallet, order.PayeeWallet);
|
||||||
|
|
||||||
|
await nats.PublishAsync(PaymentOrderEvent.Type, JsonSerializer.SerializeToUtf8Bytes(new PaymentOrderEvent
|
||||||
|
{
|
||||||
|
OrderId = order.Id,
|
||||||
|
WalletId = payerWallet.Id,
|
||||||
|
AccountId = payerWallet.AccountId,
|
||||||
|
AppIdentifier = order.AppIdentifier,
|
||||||
|
ProductIdentifier = order.ProductIdentifier,
|
||||||
|
Meta = order.Meta ?? [],
|
||||||
|
Status = (int)order.Status,
|
||||||
|
}));
|
||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task NotifyOrderPaid(Order order)
|
private async Task NotifyOrderPaid(Order order, Wallet? payerWallet, Wallet? payeeWallet)
|
||||||
{
|
{
|
||||||
if (order.PayeeWallet is null) return;
|
if (payerWallet is not null)
|
||||||
var account = await db.Accounts.FirstOrDefaultAsync(a => a.Id == order.PayeeWallet.AccountId);
|
{
|
||||||
if (account is null) return;
|
var account = await db.Accounts
|
||||||
|
.Where(a => a.Id == payerWallet.AccountId)
|
||||||
AccountService.SetCultureInfo(account);
|
.FirstOrDefaultAsync();
|
||||||
|
if (account is null) return;
|
||||||
|
|
||||||
// Due to ID is uuid, it longer than 8 words for sure
|
AccountService.SetCultureInfo(account);
|
||||||
var readableOrderId = order.Id.ToString().Replace("-", "")[..8];
|
|
||||||
var readableOrderRemark = order.Remarks ?? $"#{readableOrderId}";
|
|
||||||
|
|
||||||
|
// Due to ID is uuid, it longer than 8 words for sure
|
||||||
await pusher.SendPushNotificationToUserAsync(
|
var readableOrderId = order.Id.ToString().Replace("-", "")[..8];
|
||||||
new SendPushNotificationToUserRequest
|
var readableOrderRemark = order.Remarks ?? $"#{readableOrderId}";
|
||||||
{
|
|
||||||
UserId = account.Id.ToString(),
|
|
||||||
Notification = new PushNotification
|
await pusher.SendPushNotificationToUserAsync(
|
||||||
|
new SendPushNotificationToUserRequest
|
||||||
{
|
{
|
||||||
Topic = "wallets.orders.paid",
|
UserId = account.Id.ToString(),
|
||||||
Title = localizer["OrderPaidTitle", $"#{readableOrderId}"],
|
Notification = new PushNotification
|
||||||
Body = localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture), order.Currency,
|
{
|
||||||
readableOrderRemark],
|
Topic = "wallets.orders.paid",
|
||||||
IsSavable = true
|
Title = localizer["OrderPaidTitle", $"#{readableOrderId}"],
|
||||||
|
Body = localizer["OrderPaidBody", order.Amount.ToString(CultureInfo.InvariantCulture),
|
||||||
|
order.Currency,
|
||||||
|
readableOrderRemark],
|
||||||
|
IsSavable = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (payeeWallet is not null)
|
||||||
|
{
|
||||||
|
var account = await db.Accounts
|
||||||
|
.Where(a => a.Id == payeeWallet.AccountId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (account is null) return;
|
||||||
|
|
||||||
|
AccountService.SetCultureInfo(account);
|
||||||
|
|
||||||
|
// Due to ID is uuid, it longer than 8 words for sure
|
||||||
|
var readableOrderId = order.Id.ToString().Replace("-", "")[..8];
|
||||||
|
var readableOrderRemark = order.Remarks ?? $"#{readableOrderId}";
|
||||||
|
|
||||||
|
await pusher.SendPushNotificationToUserAsync(
|
||||||
|
new SendPushNotificationToUserRequest
|
||||||
|
{
|
||||||
|
UserId = account.Id.ToString(),
|
||||||
|
Notification = new PushNotification
|
||||||
|
{
|
||||||
|
Topic = "wallets.orders.received",
|
||||||
|
Title = localizer["OrderReceivedTitle", $"#{readableOrderId}"],
|
||||||
|
Body = localizer["OrderReceivedBody", order.Amount.ToString(CultureInfo.InvariantCulture),
|
||||||
|
order.Currency,
|
||||||
|
readableOrderRemark],
|
||||||
|
IsSavable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Order> CancelOrderAsync(Guid orderId)
|
public async Task<Order> CancelOrderAsync(Guid orderId)
|
||||||
|
@@ -13,10 +13,10 @@ public class PaymentServiceGrpc(PaymentService paymentService) : Shared.Proto.Pa
|
|||||||
request.Currency,
|
request.Currency,
|
||||||
decimal.Parse(request.Amount),
|
decimal.Parse(request.Amount),
|
||||||
request.Expiration is not null ? Duration.FromSeconds(request.Expiration.Seconds) : null,
|
request.Expiration is not null ? Duration.FromSeconds(request.Expiration.Seconds) : null,
|
||||||
request.HasAppIdentifier ? request.AppIdentifier : null,
|
request.HasAppIdentifier ? request.AppIdentifier : Order.InternalAppIdentifier,
|
||||||
// Assuming meta is a JSON string
|
request.HasProductIdentifier ? request.ProductIdentifier : null,
|
||||||
request.HasMeta
|
request.HasMeta
|
||||||
? System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(request.Meta.ToStringUtf8())
|
? GrpcTypeHelper.ConvertByteStringToObject<Dictionary<string, object>>(request.Meta)
|
||||||
: null,
|
: null,
|
||||||
request.Reuseable
|
request.Reuseable
|
||||||
);
|
);
|
||||||
|
@@ -86,7 +86,7 @@ public class SubscriptionRenewalJob(
|
|||||||
if (wallet is null) continue;
|
if (wallet is null) continue;
|
||||||
|
|
||||||
// Process automatic payment from wallet
|
// Process automatic payment from wallet
|
||||||
await paymentService.PayOrderAsync(order.Id, wallet.Id);
|
await paymentService.PayOrderAsync(order.Id, wallet);
|
||||||
|
|
||||||
// Update subscription details
|
// Update subscription details
|
||||||
subscription.BegunAt = subscription.EndedAt!.Value;
|
subscription.BegunAt = subscription.EndedAt!.Value;
|
||||||
|
@@ -8,11 +8,7 @@ public class WalletServiceGrpc(WalletService walletService) : Shared.Proto.Walle
|
|||||||
public override async Task<Shared.Proto.Wallet> GetWallet(GetWalletRequest request, ServerCallContext context)
|
public override async Task<Shared.Proto.Wallet> GetWallet(GetWalletRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var wallet = await walletService.GetWalletAsync(Guid.Parse(request.AccountId));
|
var wallet = await walletService.GetWalletAsync(Guid.Parse(request.AccountId));
|
||||||
if (wallet == null)
|
return wallet == null ? throw new RpcException(new Status(StatusCode.NotFound, "Wallet not found.")) : wallet.ToProtoValue();
|
||||||
{
|
|
||||||
throw new RpcException(new Status(StatusCode.NotFound, "Wallet not found."));
|
|
||||||
}
|
|
||||||
return wallet.ToProtoValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<Shared.Proto.Wallet> CreateWallet(CreateWalletRequest request, ServerCallContext context)
|
public override async Task<Shared.Proto.Wallet> CreateWallet(CreateWalletRequest request, ServerCallContext context)
|
||||||
|
@@ -123,6 +123,7 @@ message CreateOrderRequest {
|
|||||||
string amount = 3;
|
string amount = 3;
|
||||||
optional google.protobuf.Duration expiration = 4;
|
optional google.protobuf.Duration expiration = 4;
|
||||||
optional string app_identifier = 5;
|
optional string app_identifier = 5;
|
||||||
|
optional string product_identifier = 8;
|
||||||
// Using bytes for meta to represent JSON.
|
// Using bytes for meta to represent JSON.
|
||||||
optional bytes meta = 6;
|
optional bytes meta = 6;
|
||||||
bool reuseable = 7;
|
bool reuseable = 7;
|
||||||
@@ -135,6 +136,7 @@ message Order {
|
|||||||
string amount = 4;
|
string amount = 4;
|
||||||
google.protobuf.Timestamp expired_at = 5;
|
google.protobuf.Timestamp expired_at = 5;
|
||||||
optional string app_identifier = 6;
|
optional string app_identifier = 6;
|
||||||
|
optional string product_identifier = 12;
|
||||||
// Using bytes for meta to represent JSON.
|
// Using bytes for meta to represent JSON.
|
||||||
optional bytes meta = 7;
|
optional bytes meta = 7;
|
||||||
OrderStatus status = 8;
|
OrderStatus status = 8;
|
||||||
|
14
DysonNetwork.Shared/Stream/PaymentEvent.cs
Normal file
14
DysonNetwork.Shared/Stream/PaymentEvent.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace DysonNetwork.Shared.Stream;
|
||||||
|
|
||||||
|
public class PaymentOrderEvent
|
||||||
|
{
|
||||||
|
public static string Type => "payments.orders";
|
||||||
|
|
||||||
|
public Guid OrderId { get; set; }
|
||||||
|
public Guid WalletId { get; set; }
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
public string? AppIdentifier { get; set; }
|
||||||
|
public string? ProductIdentifier { get; set; }
|
||||||
|
public Dictionary<string, object> Meta { get; set; } = null!;
|
||||||
|
public int Status { get; set; }
|
||||||
|
}
|
@@ -31,6 +31,7 @@ public class AppDatabase(
|
|||||||
|
|
||||||
public DbSet<Post.Post> Posts { get; set; } = null!;
|
public DbSet<Post.Post> Posts { get; set; } = null!;
|
||||||
public DbSet<PostReaction> PostReactions { get; set; } = null!;
|
public DbSet<PostReaction> PostReactions { get; set; } = null!;
|
||||||
|
public DbSet<PostAward> PostAwards { get; set; } = null!;
|
||||||
public DbSet<PostTag> PostTags { get; set; } = null!;
|
public DbSet<PostTag> PostTags { get; set; } = null!;
|
||||||
public DbSet<PostCategory> PostCategories { get; set; } = null!;
|
public DbSet<PostCategory> PostCategories { get; set; } = null!;
|
||||||
public DbSet<PostCollection> PostCollections { get; set; } = null!;
|
public DbSet<PostCollection> PostCollections { get; set; } = null!;
|
||||||
|
@@ -51,6 +51,7 @@ public class Post : ModelBase, IIdentifiedResource, IActivity
|
|||||||
public int ViewsTotal { get; set; }
|
public int ViewsTotal { get; set; }
|
||||||
public int Upvotes { get; set; }
|
public int Upvotes { get; set; }
|
||||||
public int Downvotes { get; set; }
|
public int Downvotes { get; set; }
|
||||||
|
public decimal AwardedScore { get; set; }
|
||||||
[NotMapped] public Dictionary<string, int> ReactionsCount { get; set; } = new();
|
[NotMapped] public Dictionary<string, int> ReactionsCount { get; set; } = new();
|
||||||
[NotMapped] public int RepliesCount { get; set; }
|
[NotMapped] public int RepliesCount { get; set; }
|
||||||
[NotMapped] public Dictionary<string, bool>? ReactionsMade { get; set; }
|
[NotMapped] public Dictionary<string, bool>? ReactionsMade { get; set; }
|
||||||
@@ -73,6 +74,7 @@ public class Post : ModelBase, IIdentifiedResource, IActivity
|
|||||||
public Guid PublisherId { get; set; }
|
public Guid PublisherId { get; set; }
|
||||||
public Publisher.Publisher Publisher { get; set; } = null!;
|
public Publisher.Publisher Publisher { get; set; } = null!;
|
||||||
|
|
||||||
|
public ICollection<PostAward> Awards { 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>();
|
||||||
@@ -168,3 +170,15 @@ public class PostReaction : ModelBase
|
|||||||
[JsonIgnore] public Post Post { get; set; } = null!;
|
[JsonIgnore] public Post Post { get; set; } = null!;
|
||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PostAward : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
public PostReactionAttitude Attitude { get; set; }
|
||||||
|
[MaxLength(4096)] public string? Message { get; set; }
|
||||||
|
|
||||||
|
public Guid PostId { get; set; }
|
||||||
|
[JsonIgnore] public Post Post { get; set; } = null!;
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
}
|
@@ -579,6 +579,38 @@ public class PostController(
|
|||||||
return Ok(reaction);
|
return Ok(reaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PostAwardRequest
|
||||||
|
{
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
[MaxLength(4096)] public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id:guid}/awards")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<PostAward>> AwardPost(Guid id, [FromBody] PostAwardRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var friendsResponse =
|
||||||
|
await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
|
{ AccountId = currentUser.Id.ToString() });
|
||||||
|
var userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
|
var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
|
||||||
|
|
||||||
|
var post = await db.Posts
|
||||||
|
.Where(e => e.Id == id)
|
||||||
|
.Include(e => e.Publisher)
|
||||||
|
.FilterWithVisibility(currentUser, userFriends, userPublishers)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (post is null) return NotFound();
|
||||||
|
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
|
// TODO Make payment, add record
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
public class PostPinRequest
|
public class PostPinRequest
|
||||||
{
|
{
|
||||||
[Required] public PostPinMode Mode { get; set; }
|
[Required] public PostPinMode Mode { get; set; }
|
||||||
@@ -600,7 +632,7 @@ public class PostController(
|
|||||||
var accountId = Guid.Parse(currentUser.Id);
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor))
|
if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor))
|
||||||
return StatusCode(403, "You are not an editor of this publisher");
|
return StatusCode(403, "You are not an editor of this publisher");
|
||||||
|
|
||||||
if (request.Mode == PostPinMode.RealmPage && post.RealmId != null)
|
if (request.Mode == PostPinMode.RealmPage && post.RealmId != null)
|
||||||
{
|
{
|
||||||
if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, RealmMemberRole.Moderator))
|
if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, RealmMemberRole.Moderator))
|
||||||
@@ -644,11 +676,11 @@ public class PostController(
|
|||||||
.Include(e => e.RepliedPost)
|
.Include(e => e.RepliedPost)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
|
|
||||||
var accountId = Guid.Parse(currentUser.Id);
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor))
|
if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor))
|
||||||
return StatusCode(403, "You are not an editor of this publisher");
|
return StatusCode(403, "You are not an editor of this publisher");
|
||||||
|
|
||||||
if (post is { PinMode: PostPinMode.RealmPage, RealmId: not null })
|
if (post is { PinMode: PostPinMode.RealmPage, RealmId: not null })
|
||||||
{
|
{
|
||||||
if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, RealmMemberRole.Moderator))
|
if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, RealmMemberRole.Moderator))
|
||||||
@@ -807,7 +839,7 @@ public class PostController(
|
|||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
|
|
||||||
if (!await pub.IsMemberWithRole(post.Publisher.Id, Guid.Parse(currentUser.Id),
|
if (!await pub.IsMemberWithRole(post.Publisher.Id, Guid.Parse(currentUser.Id),
|
||||||
Publisher.PublisherMemberRole.Editor))
|
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);
|
||||||
|
@@ -13,7 +13,7 @@ public class BroadcastEventHandler(
|
|||||||
{
|
{
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
await foreach (var msg in nats.SubscribeAsync<byte[]>("accounts.deleted", cancellationToken: stoppingToken))
|
await foreach (var msg in nats.SubscribeAsync<byte[]>(AccountDeletedEvent.Type, cancellationToken: stoppingToken))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user