✨ Embeddable funds
✨ Chat message embeddable poll
This commit is contained in:
@@ -1,6 +1,3 @@
|
|||||||
using DysonNetwork.Shared.Models;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using NodaTime;
|
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Wallet;
|
namespace DysonNetwork.Pass.Wallet;
|
||||||
|
|||||||
@@ -680,6 +680,22 @@ public class PaymentService(
|
|||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<SnWalletFund> GetWalletFundAsync(Guid fundId)
|
||||||
|
{
|
||||||
|
var fund = await db.WalletFunds
|
||||||
|
.Include(f => f.Recipients)
|
||||||
|
.ThenInclude(r => r.RecipientAccount)
|
||||||
|
.ThenInclude(a => a.Profile)
|
||||||
|
.Include(f => f.CreatorAccount)
|
||||||
|
.ThenInclude(a => a.Profile)
|
||||||
|
.FirstOrDefaultAsync(f => f.Id == fundId);
|
||||||
|
|
||||||
|
if (fund == null)
|
||||||
|
throw new InvalidOperationException("Fund not found");
|
||||||
|
|
||||||
|
return fund;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<WalletOverview> GetWalletOverviewAsync(Guid accountId, DateTime? startDate = null, DateTime? endDate = null)
|
public async Task<WalletOverview> GetWalletOverviewAsync(Guid accountId, DateTime? startDate = null, DateTime? endDate = null)
|
||||||
{
|
{
|
||||||
var wallet = await wat.GetWalletAsync(accountId);
|
var wallet = await wat.GetWalletAsync(accountId);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace DysonNetwork.Pass.Wallet;
|
|||||||
public class PaymentServiceGrpc(PaymentService paymentService)
|
public class PaymentServiceGrpc(PaymentService paymentService)
|
||||||
: Shared.Proto.PaymentService.PaymentServiceBase
|
: Shared.Proto.PaymentService.PaymentServiceBase
|
||||||
{
|
{
|
||||||
public override async Task<Shared.Proto.Order> CreateOrder(
|
public override async Task<Order> CreateOrder(
|
||||||
CreateOrderRequest request,
|
CreateOrderRequest request,
|
||||||
ServerCallContext context
|
ServerCallContext context
|
||||||
)
|
)
|
||||||
@@ -31,7 +31,7 @@ public class PaymentServiceGrpc(PaymentService paymentService)
|
|||||||
return order.ToProtoValue();
|
return order.ToProtoValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<Shared.Proto.Transaction> CreateTransactionWithAccount(
|
public override async Task<Transaction> CreateTransactionWithAccount(
|
||||||
CreateTransactionWithAccountRequest request,
|
CreateTransactionWithAccountRequest request,
|
||||||
ServerCallContext context
|
ServerCallContext context
|
||||||
)
|
)
|
||||||
@@ -87,7 +87,7 @@ public class PaymentServiceGrpc(PaymentService paymentService)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<Shared.Proto.Transaction> Transfer(
|
public override async Task<Transaction> Transfer(
|
||||||
TransferRequest request,
|
TransferRequest request,
|
||||||
ServerCallContext context
|
ServerCallContext context
|
||||||
)
|
)
|
||||||
@@ -100,5 +100,13 @@ public class PaymentServiceGrpc(PaymentService paymentService)
|
|||||||
);
|
);
|
||||||
return transaction.ToProtoValue();
|
return transaction.ToProtoValue();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public override async Task<WalletFund> GetWalletFund(
|
||||||
|
GetWalletFundRequest request,
|
||||||
|
ServerCallContext context
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var walletFund = await paymentService.GetWalletFundAsync(Guid.Parse(request.FundId));
|
||||||
|
return walletFund.ToProtoValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ namespace DysonNetwork.Pass.Wallet;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/api/wallets")]
|
[Route("/api/wallets")]
|
||||||
public class WalletController(AppDatabase db, WalletService ws, PaymentService payment, AuthService auth, ICacheService cache) : ControllerBase
|
public class WalletController(
|
||||||
|
AppDatabase db,
|
||||||
|
WalletService ws,
|
||||||
|
PaymentService payment,
|
||||||
|
AuthService auth,
|
||||||
|
ICacheService cache
|
||||||
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@@ -154,7 +160,8 @@ public class WalletController(AppDatabase db, WalletService ws, PaymentService p
|
|||||||
|
|
||||||
var query = db.PaymentOrders.AsQueryable()
|
var query = db.PaymentOrders.AsQueryable()
|
||||||
.Include(o => o.Transaction)
|
.Include(o => o.Transaction)
|
||||||
.Where(o => o.Transaction != null && (o.Transaction.PayeeWalletId == accountWallet.Id || o.Transaction.PayerWalletId == accountWallet.Id))
|
.Where(o => o.Transaction != null && (o.Transaction.PayeeWalletId == accountWallet.Id ||
|
||||||
|
o.Transaction.PayerWalletId == accountWallet.Id))
|
||||||
.AsQueryable();
|
.AsQueryable();
|
||||||
|
|
||||||
var orderCount = await query.CountAsync();
|
var orderCount = await query.CountAsync();
|
||||||
@@ -326,7 +333,7 @@ public class WalletController(AppDatabase db, WalletService ws, PaymentService p
|
|||||||
return Ok(funds);
|
return Ok(funds);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("funds/{id}")]
|
[HttpGet("funds/{id:guid}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<SnWalletFund>> GetFund(Guid id)
|
public async Task<ActionResult<SnWalletFund>> GetFund(Guid id)
|
||||||
{
|
{
|
||||||
@@ -340,20 +347,13 @@ public class WalletController(AppDatabase db, WalletService ws, PaymentService p
|
|||||||
.ThenInclude(a => a.Profile)
|
.ThenInclude(a => a.Profile)
|
||||||
.FirstOrDefaultAsync(f => f.Id == id);
|
.FirstOrDefaultAsync(f => f.Id == id);
|
||||||
|
|
||||||
if (fund == null)
|
if (fund is null)
|
||||||
return NotFound("Fund not found");
|
return NotFound("Fund not found");
|
||||||
|
|
||||||
// Check if user is creator or recipient
|
|
||||||
var isCreator = fund.CreatorAccountId == currentUser.Id;
|
|
||||||
var isRecipient = fund.Recipients.Any(r => r.RecipientAccountId == currentUser.Id);
|
|
||||||
|
|
||||||
if (!isCreator && !isRecipient)
|
|
||||||
return Forbid("You don't have permission to view this fund");
|
|
||||||
|
|
||||||
return Ok(fund);
|
return Ok(fund);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("funds/{id}/receive")]
|
[HttpPost("funds/{id:guid}/receive")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<SnWalletTransaction>> ReceiveFund(Guid id)
|
public async Task<ActionResult<SnWalletTransaction>> ReceiveFund(Guid id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ service PaymentService {
|
|||||||
rpc CancelOrder(CancelOrderRequest) returns (Order);
|
rpc CancelOrder(CancelOrderRequest) returns (Order);
|
||||||
rpc RefundOrder(RefundOrderRequest) returns (RefundOrderResponse);
|
rpc RefundOrder(RefundOrderRequest) returns (RefundOrderResponse);
|
||||||
rpc Transfer(TransferRequest) returns (Transaction);
|
rpc Transfer(TransferRequest) returns (Transaction);
|
||||||
|
rpc GetWalletFund(GetWalletFundRequest) returns (WalletFund);
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateOrderRequest {
|
message CreateOrderRequest {
|
||||||
@@ -287,3 +288,7 @@ message TransferRequest {
|
|||||||
string currency = 3;
|
string currency = 3;
|
||||||
string amount = 4;
|
string amount = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetWalletFundRequest {
|
||||||
|
string fund_id = 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ using DysonNetwork.Shared.Data;
|
|||||||
using DysonNetwork.Shared.Models;
|
using DysonNetwork.Shared.Models;
|
||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
using DysonNetwork.Sphere.Autocompletion;
|
using DysonNetwork.Sphere.Autocompletion;
|
||||||
|
using DysonNetwork.Sphere.Poll;
|
||||||
|
using DysonNetwork.Sphere.Wallet;
|
||||||
|
using DysonNetwork.Sphere.WebReader;
|
||||||
|
using Grpc.Core;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -19,7 +23,9 @@ public partial class ChatController(
|
|||||||
ChatRoomService crs,
|
ChatRoomService crs,
|
||||||
FileService.FileServiceClient files,
|
FileService.FileServiceClient files,
|
||||||
AccountService.AccountServiceClient accounts,
|
AccountService.AccountServiceClient accounts,
|
||||||
AutocompletionService aus
|
AutocompletionService aus,
|
||||||
|
PaymentService.PaymentServiceClient paymentClient,
|
||||||
|
PollService polls
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
public class MarkMessageReadRequest
|
public class MarkMessageReadRequest
|
||||||
@@ -66,6 +72,8 @@ public partial class ChatController(
|
|||||||
{
|
{
|
||||||
[MaxLength(4096)] public string? Content { get; set; }
|
[MaxLength(4096)] public string? Content { get; set; }
|
||||||
[MaxLength(36)] public string? Nonce { get; set; }
|
[MaxLength(36)] public string? Nonce { get; set; }
|
||||||
|
public Guid? FundId { get; set; }
|
||||||
|
public Guid? PollId { get; set; }
|
||||||
public List<string>? AttachmentsId { get; set; }
|
public List<string>? AttachmentsId { get; set; }
|
||||||
public Dictionary<string, object>? Meta { get; set; }
|
public Dictionary<string, object>? Meta { get; set; }
|
||||||
public Guid? RepliedMessageId { get; set; }
|
public Guid? RepliedMessageId { get; set; }
|
||||||
@@ -227,13 +235,52 @@ public partial class ChatController(
|
|||||||
|
|
||||||
request.Content = TextSanitizer.Sanitize(request.Content);
|
request.Content = TextSanitizer.Sanitize(request.Content);
|
||||||
if (string.IsNullOrWhiteSpace(request.Content) &&
|
if (string.IsNullOrWhiteSpace(request.Content) &&
|
||||||
(request.AttachmentsId == null || request.AttachmentsId.Count == 0))
|
(request.AttachmentsId == null || request.AttachmentsId.Count == 0) &&
|
||||||
|
!request.FundId.HasValue)
|
||||||
return BadRequest("You cannot send an empty message.");
|
return BadRequest("You cannot send an empty message.");
|
||||||
|
|
||||||
var member = await crs.GetRoomMember(Guid.Parse(currentUser.Id), roomId);
|
var member = await crs.GetRoomMember(Guid.Parse(currentUser.Id), roomId);
|
||||||
if (member == null || member.Role < ChatMemberRole.Member)
|
if (member == null || member.Role < ChatMemberRole.Member)
|
||||||
return StatusCode(403, "You need to be a normal member to send messages here.");
|
return StatusCode(403, "You need to be a normal member to send messages here.");
|
||||||
|
|
||||||
|
// Validate fund if provided
|
||||||
|
if (request.FundId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fundResponse = await paymentClient.GetWalletFundAsync(new GetWalletFundRequest
|
||||||
|
{
|
||||||
|
FundId = request.FundId.Value.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the fund was created by the current user
|
||||||
|
if (fundResponse.CreatorAccountId != member.AccountId.ToString())
|
||||||
|
return BadRequest("You can only share funds that you created.");
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
|
||||||
|
{
|
||||||
|
return BadRequest("The specified fund does not exist.");
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.InvalidArgument)
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid fund ID.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate poll if provided
|
||||||
|
if (request.PollId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pollEmbed = await polls.MakePollEmbed(request.PollId.Value);
|
||||||
|
// Poll validation is handled by the MakePollEmbed method
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var message = new SnChatMessage
|
var message = new SnChatMessage
|
||||||
{
|
{
|
||||||
Type = "text",
|
Type = "text",
|
||||||
@@ -242,6 +289,36 @@ public partial class ChatController(
|
|||||||
Nonce = request.Nonce ?? Guid.NewGuid().ToString(),
|
Nonce = request.Nonce ?? Guid.NewGuid().ToString(),
|
||||||
Meta = request.Meta ?? new Dictionary<string, object>(),
|
Meta = request.Meta ?? new Dictionary<string, object>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add embed for fund if provided
|
||||||
|
if (request.FundId.HasValue)
|
||||||
|
{
|
||||||
|
var fundEmbed = new FundEmbed { Id = request.FundId.Value };
|
||||||
|
message.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!message.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
message.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)message.Meta["embeds"];
|
||||||
|
embeds.Add(EmbeddableBase.ToDictionary(fundEmbed));
|
||||||
|
message.Meta["embeds"] = embeds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add embed for poll if provided
|
||||||
|
if (request.PollId.HasValue)
|
||||||
|
{
|
||||||
|
var pollEmbed = await polls.MakePollEmbed(request.PollId.Value);
|
||||||
|
message.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!message.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
message.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)message.Meta["embeds"];
|
||||||
|
embeds.Add(EmbeddableBase.ToDictionary(pollEmbed));
|
||||||
|
message.Meta["embeds"] = embeds;
|
||||||
|
}
|
||||||
if (request.Content is not null)
|
if (request.Content is not null)
|
||||||
message.Content = request.Content;
|
message.Content = request.Content;
|
||||||
if (request.AttachmentsId is not null)
|
if (request.AttachmentsId is not null)
|
||||||
@@ -330,6 +407,95 @@ public partial class ChatController(
|
|||||||
request.ForwardedMessageId, roomId, accountId);
|
request.ForwardedMessageId, roomId, accountId);
|
||||||
message.MembersMentioned = updatedMentions;
|
message.MembersMentioned = updatedMentions;
|
||||||
|
|
||||||
|
// Handle fund embeds for update
|
||||||
|
if (request.FundId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fundResponse = await paymentClient.GetWalletFundAsync(new GetWalletFundRequest
|
||||||
|
{
|
||||||
|
FundId = request.FundId.Value.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the fund was created by the current user
|
||||||
|
if (fundResponse.CreatorAccountId != accountId.ToString())
|
||||||
|
return BadRequest("You can only share funds that you created.");
|
||||||
|
|
||||||
|
var fundEmbed = new FundEmbed { Id = request.FundId.Value };
|
||||||
|
message.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!message.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
message.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)message.Meta["embeds"];
|
||||||
|
// Remove all old fund embeds
|
||||||
|
embeds.RemoveAll(e =>
|
||||||
|
e.TryGetValue("type", out var type) && type.ToString() == "fund"
|
||||||
|
);
|
||||||
|
embeds.Add(EmbeddableBase.ToDictionary(fundEmbed));
|
||||||
|
message.Meta["embeds"] = embeds;
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
|
||||||
|
{
|
||||||
|
return BadRequest("The specified fund does not exist.");
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.InvalidArgument)
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid fund ID.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!message.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
message.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)message.Meta["embeds"];
|
||||||
|
// Remove all old fund embeds
|
||||||
|
embeds.RemoveAll(e => e.TryGetValue("type", out var type) && type.ToString() == "fund");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle poll embeds for update
|
||||||
|
if (request.PollId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pollEmbed = await polls.MakePollEmbed(request.PollId.Value);
|
||||||
|
message.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!message.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
message.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)message.Meta["embeds"];
|
||||||
|
// Remove all old poll embeds
|
||||||
|
embeds.RemoveAll(e =>
|
||||||
|
e.TryGetValue("type", out var type) && type.ToString() == "poll"
|
||||||
|
);
|
||||||
|
embeds.Add(EmbeddableBase.ToDictionary(pollEmbed));
|
||||||
|
message.Meta["embeds"] = embeds;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BadRequest(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!message.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
message.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)message.Meta["embeds"];
|
||||||
|
// Remove all old poll embeds
|
||||||
|
embeds.RemoveAll(e => e.TryGetValue("type", out var type) && type.ToString() == "poll");
|
||||||
|
}
|
||||||
|
|
||||||
// Call service method to update the message
|
// Call service method to update the message
|
||||||
await cs.UpdateMessageAsync(
|
await cs.UpdateMessageAsync(
|
||||||
message,
|
message,
|
||||||
|
|||||||
@@ -31,10 +31,4 @@ public class PollEmbed : EmbeddableBase
|
|||||||
public override string Type => "poll";
|
public override string Type => "poll";
|
||||||
|
|
||||||
public required Guid Id { get; set; }
|
public required Guid Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Do not store this to the database
|
|
||||||
/// Only set this when sending the embed
|
|
||||||
/// </summary>
|
|
||||||
public PollWithStats? Poll { get; set; }
|
|
||||||
}
|
}
|
||||||
@@ -300,18 +300,6 @@ public class PollService(AppDatabase db, ICacheService cache)
|
|||||||
var poll = await db.Polls
|
var poll = await db.Polls
|
||||||
.Where(e => e.Id == pollId)
|
.Where(e => e.Id == pollId)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (poll is null)
|
return poll is null ? throw new Exception("Poll not found") : new PollEmbed { Id = poll.Id };
|
||||||
throw new Exception("Poll not found");
|
|
||||||
return new PollEmbed { Id = poll.Id };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<PollEmbed> LoadPollEmbed(Guid pollId, Guid? accountId)
|
|
||||||
{
|
|
||||||
var poll = await GetPoll(pollId) ?? throw new Exception("Poll not found");
|
|
||||||
var pollWithStats = PollWithStats.FromPoll(poll);
|
|
||||||
pollWithStats.Stats = await GetPollStats(poll.Id);
|
|
||||||
if (accountId is not null)
|
|
||||||
pollWithStats.UserAnswer = await GetPollAnswer(poll.Id, accountId.Value);
|
|
||||||
return new PollEmbed { Id = poll.Id, Poll = pollWithStats };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ using DysonNetwork.Shared.Models;
|
|||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
using DysonNetwork.Sphere.Poll;
|
using DysonNetwork.Sphere.Poll;
|
||||||
|
using DysonNetwork.Sphere.Wallet;
|
||||||
using DysonNetwork.Sphere.WebReader;
|
using DysonNetwork.Sphere.WebReader;
|
||||||
|
using Grpc.Core;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -519,6 +521,7 @@ public class PostController(
|
|||||||
public Guid? RealmId { get; set; }
|
public Guid? RealmId { get; set; }
|
||||||
|
|
||||||
public Guid? PollId { get; set; }
|
public Guid? PollId { get; set; }
|
||||||
|
public Guid? FundId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@@ -536,7 +539,7 @@ public class PostController(
|
|||||||
|
|
||||||
var accountId = Guid.Parse(currentUser.Id);
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
Shared.Models.SnPublisher? publisher;
|
SnPublisher? publisher;
|
||||||
if (pubName is null)
|
if (pubName is null)
|
||||||
{
|
{
|
||||||
// Use the first personal publisher
|
// Use the first personal publisher
|
||||||
@@ -629,6 +632,40 @@ public class PostController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.FundId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fundResponse = await payments.GetWalletFundAsync(new GetWalletFundRequest
|
||||||
|
{
|
||||||
|
FundId = request.FundId.Value.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the fund was created by the current user
|
||||||
|
if (fundResponse.CreatorAccountId != currentUser.Id)
|
||||||
|
return BadRequest("You can only share funds that you created.");
|
||||||
|
|
||||||
|
var fundEmbed = new FundEmbed { Id = request.FundId.Value };
|
||||||
|
post.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!post.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
post.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)post.Meta["embeds"];
|
||||||
|
embeds.Add(EmbeddableBase.ToDictionary(fundEmbed));
|
||||||
|
post.Meta["embeds"] = embeds;
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
|
||||||
|
{
|
||||||
|
return BadRequest("The specified fund does not exist.");
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.InvalidArgument)
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid fund ID.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
post = await ps.PostAsync(
|
post = await ps.PostAsync(
|
||||||
@@ -1072,6 +1109,57 @@ public class PostController(
|
|||||||
embeds.RemoveAll(e => e.TryGetValue("type", out var type) && type.ToString() == "poll");
|
embeds.RemoveAll(e => e.TryGetValue("type", out var type) && type.ToString() == "poll");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle fund embeds
|
||||||
|
if (request.FundId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fundResponse = await payments.GetWalletFundAsync(new GetWalletFundRequest
|
||||||
|
{
|
||||||
|
FundId = request.FundId.Value.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the fund was created by the current user
|
||||||
|
if (fundResponse.CreatorAccountId != currentUser.Id)
|
||||||
|
return BadRequest("You can only share funds that you created.");
|
||||||
|
|
||||||
|
var fundEmbed = new FundEmbed { Id = request.FundId.Value };
|
||||||
|
post.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!post.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
post.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)post.Meta["embeds"];
|
||||||
|
// Remove all old fund embeds
|
||||||
|
embeds.RemoveAll(e =>
|
||||||
|
e.TryGetValue("type", out var type) && type.ToString() == "fund"
|
||||||
|
);
|
||||||
|
embeds.Add(EmbeddableBase.ToDictionary(fundEmbed));
|
||||||
|
post.Meta["embeds"] = embeds;
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
|
||||||
|
{
|
||||||
|
return BadRequest("The specified fund does not exist.");
|
||||||
|
}
|
||||||
|
catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.InvalidArgument)
|
||||||
|
{
|
||||||
|
return BadRequest("Invalid fund ID.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
post.Meta ??= new Dictionary<string, object>();
|
||||||
|
if (
|
||||||
|
!post.Meta.TryGetValue("embeds", out var existingEmbeds)
|
||||||
|
|| existingEmbeds is not List<EmbeddableBase>
|
||||||
|
)
|
||||||
|
post.Meta["embeds"] = new List<Dictionary<string, object>>();
|
||||||
|
var embeds = (List<Dictionary<string, object>>)post.Meta["embeds"];
|
||||||
|
// Remove all old fund embeds
|
||||||
|
embeds.RemoveAll(e => e.TryGetValue("type", out var type) && type.ToString() == "fund");
|
||||||
|
}
|
||||||
|
|
||||||
// The realm is the same as well as the poll
|
// The realm is the same as well as the poll
|
||||||
if (request.RealmId is not null)
|
if (request.RealmId is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
10
DysonNetwork.Sphere/Wallet/FundEmbed.cs
Normal file
10
DysonNetwork.Sphere/Wallet/FundEmbed.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using DysonNetwork.Sphere.WebReader;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Wallet;
|
||||||
|
|
||||||
|
public class FundEmbed : EmbeddableBase
|
||||||
|
{
|
||||||
|
public override string Type => "fund";
|
||||||
|
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user