♻️ Optimization in file uploading
This commit is contained in:
@ -1,8 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SystemClock = NodaTime.SystemClock;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
@ -22,6 +24,8 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
||||
[MaxLength(36)] public string? Nonce { get; set; }
|
||||
public List<string>? AttachmentsId { get; set; }
|
||||
public Dictionary<string, object>? Meta { get; set; }
|
||||
public Guid? RepliedMessageId { get; set; }
|
||||
public Guid? ForwardedMessageId { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet("{roomId:long}/messages")]
|
||||
@ -52,8 +56,6 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
||||
.Include(m => m.Sender)
|
||||
.Include(m => m.Sender.Account)
|
||||
.Include(m => m.Sender.Account.Profile)
|
||||
.Include(m => m.Sender.Account.Profile.Picture)
|
||||
.Include(m => m.Sender.Account.Profile.Background)
|
||||
.Include(m => m.Attachments)
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
@ -63,8 +65,42 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
||||
|
||||
return Ok(messages);
|
||||
}
|
||||
|
||||
[HttpGet("{roomId:long}/messages/{messageId:guid}")]
|
||||
public async Task<ActionResult<Message>> GetMessage(long roomId, Guid messageId)
|
||||
{
|
||||
var currentUser = HttpContext.Items["CurrentUser"] as Account.Account;
|
||||
|
||||
var room = await db.ChatRooms.FirstOrDefaultAsync(r => r.Id == roomId);
|
||||
if (room is null) return NotFound();
|
||||
|
||||
if (!room.IsPublic)
|
||||
{
|
||||
if (currentUser is null) return Unauthorized();
|
||||
|
||||
var member = await db.ChatMembers
|
||||
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (member == null || member.Role < ChatMemberRole.Normal)
|
||||
return StatusCode(403, "You are not a member of this chat room.");
|
||||
}
|
||||
|
||||
var message = await db.ChatMessages
|
||||
.Where(m => m.Id == messageId && m.ChatRoomId == roomId)
|
||||
.Include(m => m.Sender)
|
||||
.Include(m => m.Sender.Account)
|
||||
.Include(m => m.Sender.Account.Profile)
|
||||
.Include(m => m.Attachments)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (message is null) return NotFound();
|
||||
|
||||
return Ok(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[GeneratedRegex(@"@([A-Za-z0-9_-]+)")]
|
||||
[GeneratedRegex("@([A-Za-z0-9_-]+)")]
|
||||
private static partial Regex MentionRegex();
|
||||
|
||||
[HttpPost("{roomId:long}/messages")]
|
||||
@ -102,6 +138,26 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
||||
.OrderBy(f => request.AttachmentsId.IndexOf(f.Id))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (request.RepliedMessageId.HasValue)
|
||||
{
|
||||
var repliedMessage = await db.ChatMessages
|
||||
.FirstOrDefaultAsync(m => m.Id == request.RepliedMessageId.Value && m.ChatRoomId == roomId);
|
||||
if (repliedMessage == null)
|
||||
return BadRequest("The message you're replying to does not exist.");
|
||||
|
||||
message.RepliedMessageId = repliedMessage.Id;
|
||||
}
|
||||
|
||||
if (request.ForwardedMessageId.HasValue)
|
||||
{
|
||||
var forwardedMessage = await db.ChatMessages
|
||||
.FirstOrDefaultAsync(m => m.Id == request.ForwardedMessageId.Value);
|
||||
if (forwardedMessage == null)
|
||||
return BadRequest("The message you're forwarding does not exist.");
|
||||
|
||||
message.ForwardedMessageId = forwardedMessage.Id;
|
||||
}
|
||||
|
||||
if (request.Content is not null)
|
||||
{
|
||||
@ -123,14 +179,105 @@ public partial class ChatController(AppDatabase db, ChatService cs) : Controller
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPatch("{roomId:long}/messages/{messageId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> UpdateMessage([FromBody] SendMessageRequest request, long roomId, Guid messageId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var message = await db.ChatMessages
|
||||
.Include(m => m.Sender)
|
||||
.Include(m => m.Sender.Account)
|
||||
.Include(m => m.Sender.Account.Profile)
|
||||
.FirstOrDefaultAsync(m => m.Id == messageId && m.ChatRoomId == roomId);
|
||||
if (message == null) return NotFound();
|
||||
|
||||
if (message.Sender.AccountId != currentUser.Id)
|
||||
return StatusCode(403, "You can only edit your own messages.");
|
||||
|
||||
if (request.Content is not null)
|
||||
message.Content = request.Content;
|
||||
if (request.Meta is not null)
|
||||
message.Meta = request.Meta;
|
||||
|
||||
if (request.RepliedMessageId.HasValue)
|
||||
{
|
||||
var repliedMessage = await db.ChatMessages
|
||||
.FirstOrDefaultAsync(m => m.Id == request.RepliedMessageId.Value && m.ChatRoomId == roomId);
|
||||
if (repliedMessage == null)
|
||||
return BadRequest("The message you're replying to does not exist.");
|
||||
|
||||
message.RepliedMessageId = repliedMessage.Id;
|
||||
}
|
||||
|
||||
if (request.ForwardedMessageId.HasValue)
|
||||
{
|
||||
var forwardedMessage = await db.ChatMessages
|
||||
.FirstOrDefaultAsync(m => m.Id == request.ForwardedMessageId.Value);
|
||||
if (forwardedMessage == null)
|
||||
return BadRequest("The message you're forwarding does not exist.");
|
||||
|
||||
message.ForwardedMessageId = forwardedMessage.Id;
|
||||
}
|
||||
|
||||
if (request.AttachmentsId is not null)
|
||||
{
|
||||
var records = await db.Files.Where(f => request.AttachmentsId.Contains(f.Id)).ToListAsync();
|
||||
|
||||
var previous = message.Attachments?.ToDictionary(f => f.Id) ?? new Dictionary<string, CloudFile>();
|
||||
var current = records.ToDictionary(f => f.Id);
|
||||
|
||||
// Detect added files
|
||||
var added = current.Keys.Except(previous.Keys).Select(id => current[id]).ToList();
|
||||
// Detect removed files
|
||||
var removed = previous.Keys.Except(current.Keys).Select(id => previous[id]).ToList();
|
||||
|
||||
// Update attachments
|
||||
message.Attachments = request.AttachmentsId.Select(id => current[id]).ToList();
|
||||
|
||||
// Call mark usage
|
||||
var fs = HttpContext.RequestServices.GetRequiredService<Storage.FileService>();
|
||||
await fs.MarkUsageRangeAsync(added, 1);
|
||||
await fs.MarkUsageRangeAsync(removed, -1);
|
||||
}
|
||||
|
||||
message.EditedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
db.Update(message);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Ok(message);
|
||||
}
|
||||
|
||||
[HttpDelete("{roomId:long}/messages/{messageId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> DeleteMessage(long roomId, Guid messageId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var message = await db.ChatMessages
|
||||
.Include(m => m.Sender)
|
||||
.FirstOrDefaultAsync(m => m.Id == messageId && m.ChatRoomId == roomId);
|
||||
if (message == null) return NotFound();
|
||||
|
||||
if (message.Sender.AccountId != currentUser.Id)
|
||||
return StatusCode(403, "You can only delete your own messages.");
|
||||
|
||||
db.ChatMessages.Remove(message);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public class SyncRequest
|
||||
{
|
||||
[Required]
|
||||
public long LastSyncTimestamp { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet("{roomId:long}/sync")]
|
||||
[HttpPost("{roomId:long}/sync")]
|
||||
public async Task<ActionResult<SyncResponse>> GetSyncData([FromBody] SyncRequest request, long roomId)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||
|
@ -9,6 +9,9 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
public async Task<Message> SendMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||
{
|
||||
message.CreatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||
message.UpdatedAt = message.CreatedAt;
|
||||
|
||||
// First complete the save operation
|
||||
db.ChatMessages.Add(message);
|
||||
await db.SaveChangesAsync();
|
||||
@ -23,7 +26,7 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
||||
|
||||
public async Task DeliverMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||
{
|
||||
var scope = scopeFactory.CreateScope();
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
var scopedDb = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||
var scopedWs = scope.ServiceProvider.GetRequiredService<WebSocketService>();
|
||||
var scopedNty = scope.ServiceProvider.GetRequiredService<NotificationService>();
|
||||
@ -32,7 +35,7 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
||||
var tasks = new List<Task>();
|
||||
|
||||
var members = await scopedDb.ChatMembers
|
||||
.Where(m => m.ChatRoomId == message.ChatRoomId)
|
||||
.Where(m => m.ChatRoomId == message.ChatRoomId && m.AccountId != sender.AccountId)
|
||||
.Where(m => m.Notify != ChatMemberNotify.None)
|
||||
.Where(m => m.Notify != ChatMemberNotify.Mentions ||
|
||||
(message.MembersMentioned != null && message.MembersMentioned.Contains(m.Id)))
|
||||
@ -103,14 +106,17 @@ public class ChatService(AppDatabase db, IServiceScopeFactory scopeFactory)
|
||||
var timestamp = Instant.FromUnixTimeMilliseconds(lastSyncTimestamp);
|
||||
var changes = await db.ChatMessages
|
||||
.IgnoreQueryFilters()
|
||||
.Include(e => e.Sender)
|
||||
.Include(e => e.Sender.Account)
|
||||
.Include(e => e.Sender.Account.Profile)
|
||||
.Where(m => m.ChatRoomId == roomId)
|
||||
.Where(m => m.UpdatedAt > timestamp || m.DeletedAt > timestamp)
|
||||
.Select(m => new MessageChange
|
||||
{
|
||||
MessageId = m.Id,
|
||||
Action = m.DeletedAt != null ? "delete" : (m.UpdatedAt == null ? "create" : "update"),
|
||||
Action = m.DeletedAt != null ? "delete" : (m.UpdatedAt == m.CreatedAt ? "create" : "update"),
|
||||
Message = m.DeletedAt != null ? null : m,
|
||||
Timestamp = m.DeletedAt != null ? m.DeletedAt.Value : m.UpdatedAt
|
||||
Timestamp = m.DeletedAt ?? m.UpdatedAt
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
|
Reference in New Issue
Block a user