✨ Embeddable funds
✨ Chat message embeddable poll
This commit is contained in:
@@ -5,6 +5,10 @@ using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
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.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -19,7 +23,9 @@ public partial class ChatController(
|
||||
ChatRoomService crs,
|
||||
FileService.FileServiceClient files,
|
||||
AccountService.AccountServiceClient accounts,
|
||||
AutocompletionService aus
|
||||
AutocompletionService aus,
|
||||
PaymentService.PaymentServiceClient paymentClient,
|
||||
PollService polls
|
||||
) : ControllerBase
|
||||
{
|
||||
public class MarkMessageReadRequest
|
||||
@@ -66,6 +72,8 @@ public partial class ChatController(
|
||||
{
|
||||
[MaxLength(4096)] public string? Content { 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 Dictionary<string, object>? Meta { get; set; }
|
||||
public Guid? RepliedMessageId { get; set; }
|
||||
@@ -227,13 +235,52 @@ public partial class ChatController(
|
||||
|
||||
request.Content = TextSanitizer.Sanitize(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.");
|
||||
|
||||
var member = await crs.GetRoomMember(Guid.Parse(currentUser.Id), roomId);
|
||||
if (member == null || member.Role < ChatMemberRole.Member)
|
||||
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
|
||||
{
|
||||
Type = "text",
|
||||
@@ -242,6 +289,36 @@ public partial class ChatController(
|
||||
Nonce = request.Nonce ?? Guid.NewGuid().ToString(),
|
||||
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)
|
||||
message.Content = request.Content;
|
||||
if (request.AttachmentsId is not null)
|
||||
@@ -330,6 +407,95 @@ public partial class ChatController(
|
||||
request.ForwardedMessageId, roomId, accountId);
|
||||
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
|
||||
await cs.UpdateMessageAsync(
|
||||
message,
|
||||
@@ -405,4 +571,4 @@ public partial class ChatController(
|
||||
var result = await aus.GetAutocompletion(request.Content, chatId: roomId, limit: 10);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,4 @@ public class PollEmbed : EmbeddableBase
|
||||
public override string Type => "poll";
|
||||
|
||||
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
|
||||
.Where(e => e.Id == pollId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (poll is null)
|
||||
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 };
|
||||
return poll is null ? throw new Exception("Poll not found") : new PollEmbed { Id = poll.Id };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using DysonNetwork.Sphere.Poll;
|
||||
using DysonNetwork.Sphere.Wallet;
|
||||
using DysonNetwork.Sphere.WebReader;
|
||||
using Grpc.Core;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -519,6 +521,7 @@ public class PostController(
|
||||
public Guid? RealmId { get; set; }
|
||||
|
||||
public Guid? PollId { get; set; }
|
||||
public Guid? FundId { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@@ -536,7 +539,7 @@ public class PostController(
|
||||
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
|
||||
Shared.Models.SnPublisher? publisher;
|
||||
SnPublisher? publisher;
|
||||
if (pubName is null)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
post = await ps.PostAsync(
|
||||
@@ -1072,6 +1109,57 @@ public class PostController(
|
||||
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
|
||||
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