🗑️ remove Casbin dependency and related configurations
Remove Casbin package references, configurations, and unused imports across multiple files. This change simplifies the codebase by eliminating unnecessary dependencies and reducing complexity. ✨ add new chat features and improve message handling Introduce new chat features including message notifications, nicknames, and improved message handling. Enhance the WebSocket service to support new packet handlers and improve message delivery. 🗃️ add new migrations for chat-related changes Add new migrations to support the latest chat features, including changes to chat members, messages, and reactions. These migrations ensure the database schema is up-to-date with the latest code changes.
This commit is contained in:
parent
46054dfb7b
commit
f6acb3f2f0
@ -1,8 +1,6 @@
|
|||||||
using Casbin;
|
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using NodaTime;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Account;
|
namespace DysonNetwork.Sphere.Account;
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@ using System.IdentityModel.Tokens.Jwt;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Casbin;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
[request_definition]
|
|
||||||
r = sub, dom, obj, act
|
|
||||||
|
|
||||||
[policy_definition]
|
|
||||||
p = sub, dom, obj, act
|
|
||||||
|
|
||||||
[role_definition]
|
|
||||||
g = _, _, _
|
|
||||||
|
|
||||||
[policy_effect]
|
|
||||||
e = some(where (p.eft == allow))
|
|
||||||
|
|
||||||
[matchers]
|
|
||||||
m = regexMatch(r.sub, "^super:") || (g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act)
|
|
@ -1,12 +1,94 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/chat")]
|
[Route("/chat")]
|
||||||
public class ChatController : ControllerBase
|
public partial class ChatController(AppDatabase db, ChatService cs) : ControllerBase
|
||||||
{
|
{
|
||||||
public class MarkMessageReadRequest
|
public class MarkMessageReadRequest
|
||||||
{
|
{
|
||||||
public Guid MessageId { get; set; }
|
public Guid MessageId { get; set; }
|
||||||
public long ChatRoomId { get; set; }
|
public long ChatRoomId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SendMessageRequest
|
||||||
|
{
|
||||||
|
[MaxLength(4096)] public string? Content { get; set; }
|
||||||
|
public List<CloudFile>? Attachments { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"@([A-Za-z0-9_-]+)")]
|
||||||
|
private static partial Regex MentionRegex();
|
||||||
|
|
||||||
|
[HttpPost("{roomId:long}/messages")]
|
||||||
|
public async Task<ActionResult> SendMessage([FromBody] SendMessageRequest request, long roomId)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Content) && (request.Attachments == null || request.Attachments.Count == 0))
|
||||||
|
return BadRequest("You cannot send an empty message.");
|
||||||
|
|
||||||
|
var member = await db.ChatMembers
|
||||||
|
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
|
||||||
|
.Include(m => m.ChatRoom)
|
||||||
|
.Include(m => m.ChatRoom.Realm)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (member == null || member.Role < ChatMemberRole.Normal) return StatusCode(403, "You need to be a normal member to send messages here.");
|
||||||
|
|
||||||
|
var message = new Message
|
||||||
|
{
|
||||||
|
SenderId = member.Id,
|
||||||
|
ChatRoomId = roomId,
|
||||||
|
};
|
||||||
|
if (request.Content is not null)
|
||||||
|
message.Content = request.Content;
|
||||||
|
if (request.Attachments is not null)
|
||||||
|
message.Attachments = request.Attachments;
|
||||||
|
|
||||||
|
if (request.Content is not null)
|
||||||
|
{
|
||||||
|
var mentioned = MentionRegex()
|
||||||
|
.Matches(request.Content)
|
||||||
|
.Select(m => m.Groups[1].Value)
|
||||||
|
.ToList();
|
||||||
|
if (mentioned is not null && mentioned.Count > 0)
|
||||||
|
{
|
||||||
|
var mentionedMembers = await db.ChatMembers
|
||||||
|
.Where(m => mentioned.Contains(m.Account.Name))
|
||||||
|
.Select(m => m.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
message.MembersMetioned = mentionedMembers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
member.Account = currentUser;
|
||||||
|
var result = await cs.SendMessageAsync(message, member, member.ChatRoom);
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SyncRequest
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public long LastSyncTimestamp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{roomId:long}/sync")]
|
||||||
|
public async Task<ActionResult<SyncResponse>> GetSyncData([FromQuery] SyncRequest request, long roomId)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var isMember = await db.ChatMembers
|
||||||
|
.AnyAsync(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId);
|
||||||
|
if (!isMember)
|
||||||
|
return StatusCode(403, "You are not a member of this chat room.");
|
||||||
|
|
||||||
|
var response = await cs.GetSyncDataAsync(roomId, request.LastSyncTimestamp);
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
}
|
}
|
@ -35,6 +35,13 @@ public enum ChatMemberRole
|
|||||||
Normal = 0
|
Normal = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ChatMemberNotify
|
||||||
|
{
|
||||||
|
All,
|
||||||
|
Mentions,
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
public class ChatMember : ModelBase
|
public class ChatMember : ModelBase
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
@ -43,7 +50,10 @@ public class ChatMember : ModelBase
|
|||||||
public long AccountId { get; set; }
|
public long AccountId { get; set; }
|
||||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
|
|
||||||
public ChatMemberRole Role { get; set; }
|
[MaxLength(1024)] public string? Nick { get; set; }
|
||||||
|
|
||||||
|
public ChatMemberRole Role { get; set; } = ChatMemberRole.Normal;
|
||||||
|
public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All;
|
||||||
public Instant? JoinedAt { get; set; }
|
public Instant? JoinedAt { get; set; }
|
||||||
public bool IsBot { get; set; } = false;
|
public bool IsBot { get; set; } = false;
|
||||||
}
|
}
|
@ -37,7 +37,6 @@ public class ChatRoomController(AppDatabase db, FileService fs) : ControllerBase
|
|||||||
.Include(e => e.ChatRoom)
|
.Include(e => e.ChatRoom)
|
||||||
.Include(e => e.ChatRoom.Picture)
|
.Include(e => e.ChatRoom.Picture)
|
||||||
.Include(e => e.ChatRoom.Background)
|
.Include(e => e.ChatRoom.Background)
|
||||||
.Include(e => e.ChatRoom.Type == ChatRoomType.DirectMessage ? e.ChatRoom.Members : null)
|
|
||||||
.Select(m => m.ChatRoom)
|
.Select(m => m.ChatRoom)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
@ -1,9 +1,47 @@
|
|||||||
using DysonNetwork.Sphere;
|
using DysonNetwork.Sphere.Account;
|
||||||
using DysonNetwork.Sphere.Chat;
|
using DysonNetwork.Sphere.Connection;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
public class ChatService(AppDatabase db)
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
|
|
||||||
|
public class ChatService(AppDatabase db, NotificationService nty, WebSocketService ws)
|
||||||
{
|
{
|
||||||
|
public async Task<Message> SendMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||||
|
{
|
||||||
|
db.ChatMessages.Add(message);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
_ = DeliverMessageAsync(message, sender, room).ConfigureAwait(false);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeliverMessageAsync(Message message, ChatMember sender, ChatRoom room)
|
||||||
|
{
|
||||||
|
var roomSubject = room.Realm is not null ? $"{room.Name}, {room.Realm.Name}" : room.Name;
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
await foreach (
|
||||||
|
var member in db.ChatMembers
|
||||||
|
.Where(m => m.ChatRoomId == message.ChatRoomId && m.AccountId != message.Sender.AccountId)
|
||||||
|
.Where(m => m.Notify != ChatMemberNotify.None)
|
||||||
|
.Where(m => m.Notify != ChatMemberNotify.Mentions || (message.MembersMetioned != null && message.MembersMetioned.Contains(m.Id)))
|
||||||
|
.AsAsyncEnumerable()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ws.SendPacketToAccount(member.AccountId, new WebSocketPacket
|
||||||
|
{
|
||||||
|
Type = "messages.new",
|
||||||
|
Data = message
|
||||||
|
});
|
||||||
|
tasks.Add(nty.DeliveryNotification(new Notification
|
||||||
|
{
|
||||||
|
AccountId = member.AccountId,
|
||||||
|
Topic = "messages.new",
|
||||||
|
Title = $"{sender.Nick ?? sender.Account.Nick} ({roomSubject})",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task MarkMessageAsReadAsync(Guid messageId, long roomId, long userId)
|
public async Task MarkMessageAsReadAsync(Guid messageId, long roomId, long userId)
|
||||||
{
|
{
|
||||||
var existingStatus = await db.ChatStatuses
|
var existingStatus = await db.ChatStatuses
|
||||||
@ -45,4 +83,48 @@ public class ChatService(AppDatabase db)
|
|||||||
|
|
||||||
return messages.Count(m => !m.IsRead);
|
return messages.Count(m => !m.IsRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<SyncResponse> GetSyncDataAsync(long roomId, long lastSyncTimestamp)
|
||||||
|
{
|
||||||
|
var timestamp = Instant.FromUnixTimeMilliseconds(lastSyncTimestamp);
|
||||||
|
var changes = await db.ChatMessages
|
||||||
|
.IgnoreQueryFilters()
|
||||||
|
.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.EditedAt == null ? "create" : "update"),
|
||||||
|
Message = m.DeletedAt != null ? null : m,
|
||||||
|
Timestamp = m.DeletedAt != null ? m.DeletedAt.Value : m.UpdatedAt
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return new SyncResponse
|
||||||
|
{
|
||||||
|
Changes = changes,
|
||||||
|
CurrentTimestamp = SystemClock.Instance.GetCurrentInstant()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessageChangeAction
|
||||||
|
{
|
||||||
|
public const string Create = "create";
|
||||||
|
public const string Update = "update";
|
||||||
|
public const string Delete = "delete";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MessageChange
|
||||||
|
{
|
||||||
|
public Guid MessageId { get; set; }
|
||||||
|
public string Action { get; set; } = null!;
|
||||||
|
public Message? Message { get; set; }
|
||||||
|
public Instant Timestamp { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SyncResponse
|
||||||
|
{
|
||||||
|
public List<MessageChange> Changes { get; set; } = [];
|
||||||
|
public Instant CurrentTimestamp { get; set; }
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Net.Mail;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using DysonNetwork.Sphere.Storage;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Chat;
|
namespace DysonNetwork.Sphere.Chat;
|
||||||
@ -15,7 +15,7 @@ public class Message : ModelBase
|
|||||||
[Column(TypeName = "jsonb")] public List<Guid>? MembersMetioned { get; set; }
|
[Column(TypeName = "jsonb")] public List<Guid>? MembersMetioned { get; set; }
|
||||||
public Instant? EditedAt { get; set; }
|
public Instant? EditedAt { get; set; }
|
||||||
|
|
||||||
public ICollection<Attachment> Attachments { get; set; } = new List<Attachment>();
|
public ICollection<CloudFile> Attachments { get; set; } = new List<CloudFile>();
|
||||||
public ICollection<MessageReaction> Reactions { get; set; } = new List<MessageReaction>();
|
public ICollection<MessageReaction> Reactions { get; set; } = new List<MessageReaction>();
|
||||||
public ICollection<MessageStatus> Statuses { get; set; } = new List<MessageStatus>();
|
public ICollection<MessageStatus> Statuses { get; set; } = new List<MessageStatus>();
|
||||||
|
|
||||||
@ -39,6 +39,7 @@ public enum MessageReactionAttitude
|
|||||||
|
|
||||||
public class MessageReaction : ModelBase
|
public class MessageReaction : ModelBase
|
||||||
{
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
public Guid MessageId { get; set; }
|
public Guid MessageId { get; set; }
|
||||||
[JsonIgnore] public Message Message { get; set; } = null!;
|
[JsonIgnore] public Message Message { get; set; } = null!;
|
||||||
public Guid SenderId { get; set; }
|
public Guid SenderId { get; set; }
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
using System.Net.WebSockets;
|
||||||
|
using DysonNetwork.Sphere.Chat;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Connection.Handlers;
|
||||||
|
|
||||||
|
public class MessageReadHandler(AppDatabase db) : IWebSocketPacketHandler
|
||||||
|
{
|
||||||
|
public string PacketType => "message.read";
|
||||||
|
|
||||||
|
public async Task HandleAsync(Account.Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket)
|
||||||
|
{
|
||||||
|
var request = packet.GetData<Chat.ChatController.MarkMessageReadRequest>();
|
||||||
|
if (request is null)
|
||||||
|
{
|
||||||
|
await socket.SendAsync(
|
||||||
|
new ArraySegment<byte>(new WebSocketPacket
|
||||||
|
{
|
||||||
|
Type = WebSocketPacketType.Error,
|
||||||
|
ErrorMessage = "Mark message as read requires you provide the ChatRoomId and MessageId"
|
||||||
|
}.ToBytes()),
|
||||||
|
WebSocketMessageType.Binary,
|
||||||
|
true,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingStatus = await db.ChatStatuses
|
||||||
|
.FirstOrDefaultAsync(x => x.MessageId == request.MessageId && x.Sender.AccountId == currentUser.Id);
|
||||||
|
var sender = await db.ChatMembers
|
||||||
|
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == request.ChatRoomId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (sender is null)
|
||||||
|
{
|
||||||
|
await socket.SendAsync(
|
||||||
|
new ArraySegment<byte>(new WebSocketPacket
|
||||||
|
{
|
||||||
|
Type = WebSocketPacketType.Error,
|
||||||
|
ErrorMessage = "User is not a member of the chat room."
|
||||||
|
}.ToBytes()),
|
||||||
|
WebSocketMessageType.Binary,
|
||||||
|
true,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingStatus == null)
|
||||||
|
{
|
||||||
|
existingStatus = new MessageStatus
|
||||||
|
{
|
||||||
|
MessageId = request.MessageId,
|
||||||
|
SenderId = sender.Id,
|
||||||
|
};
|
||||||
|
db.ChatStatuses.Add(existingStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
using System.Net.WebSockets;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Connection;
|
||||||
|
|
||||||
|
public interface IWebSocketPacketHandler
|
||||||
|
{
|
||||||
|
string PacketType { get; }
|
||||||
|
Task HandleAsync(Account.Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket);
|
||||||
|
}
|
@ -1,11 +1,19 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
|
using DysonNetwork.Sphere.Chat;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Connection;
|
namespace DysonNetwork.Sphere.Connection;
|
||||||
|
|
||||||
public class WebSocketService(ChatService cs)
|
public class WebSocketService
|
||||||
{
|
{
|
||||||
public static readonly ConcurrentDictionary<
|
private readonly IDictionary<string, IWebSocketPacketHandler> _handlerMap;
|
||||||
|
|
||||||
|
public WebSocketService(IEnumerable<IWebSocketPacketHandler> handlers)
|
||||||
|
{
|
||||||
|
_handlerMap = handlers.ToDictionary(h => h.PacketType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly ConcurrentDictionary<
|
||||||
(long AccountId, string DeviceId),
|
(long AccountId, string DeviceId),
|
||||||
(WebSocket Socket, CancellationTokenSource Cts)
|
(WebSocket Socket, CancellationTokenSource Cts)
|
||||||
> ActiveConnections = new();
|
> ActiveConnections = new();
|
||||||
@ -17,7 +25,8 @@ public class WebSocketService(ChatService cs)
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (ActiveConnections.TryGetValue(key, out _))
|
if (ActiveConnections.TryGetValue(key, out _))
|
||||||
Disconnect(key, "Just connected somewhere else with the same identifier."); // Disconnect the previous one using the same identifier
|
Disconnect(key,
|
||||||
|
"Just connected somewhere else with the same identifier."); // Disconnect the previous one using the same identifier
|
||||||
return ActiveConnections.TryAdd(key, (socket, cts));
|
return ActiveConnections.TryAdd(key, (socket, cts));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,40 +42,58 @@ public class WebSocketService(ChatService cs)
|
|||||||
ActiveConnections.TryRemove(key, out _);
|
ActiveConnections.TryRemove(key, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandlePacket(Account.Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket)
|
public void SendPacketToAccount(long userId, WebSocketPacket packet)
|
||||||
{
|
{
|
||||||
switch (packet.Type)
|
var connections = ActiveConnections.Where(c => c.Key.AccountId == userId);
|
||||||
|
var packetBytes = packet.ToBytes();
|
||||||
|
var segment = new ArraySegment<byte>(packetBytes);
|
||||||
|
|
||||||
|
foreach (var connection in connections)
|
||||||
{
|
{
|
||||||
case "message.read":
|
connection.Value.Socket.SendAsync(
|
||||||
var request = packet.GetData<ChatController.MarkMessageReadRequest>();
|
segment,
|
||||||
if (request is null)
|
WebSocketMessageType.Binary,
|
||||||
{
|
true,
|
||||||
socket.SendAsync(
|
CancellationToken.None
|
||||||
new ArraySegment<byte>(new WebSocketPacket
|
);
|
||||||
{
|
|
||||||
Type = WebSocketPacketType.Error,
|
|
||||||
ErrorMessage = "Mark message as read requires you provide the ChatRoomId and MessageId"
|
|
||||||
}.ToBytes()),
|
|
||||||
WebSocketMessageType.Binary,
|
|
||||||
true,
|
|
||||||
CancellationToken.None
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ = cs.MarkMessageAsReadAsync(request.MessageId, currentUser.Id, currentUser.Id).ConfigureAwait(false);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
socket.SendAsync(
|
|
||||||
new ArraySegment<byte>(new WebSocketPacket
|
|
||||||
{
|
|
||||||
Type = WebSocketPacketType.Error,
|
|
||||||
ErrorMessage = $"Unprocessable packet: {packet.Type}"
|
|
||||||
}.ToBytes()),
|
|
||||||
WebSocketMessageType.Binary,
|
|
||||||
true,
|
|
||||||
CancellationToken.None
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SendPacketToDevice(string deviceId, WebSocketPacket packet)
|
||||||
|
{
|
||||||
|
var connections = ActiveConnections.Where(c => c.Key.DeviceId == deviceId);
|
||||||
|
var packetBytes = packet.ToBytes();
|
||||||
|
var segment = new ArraySegment<byte>(packetBytes);
|
||||||
|
|
||||||
|
foreach (var connection in connections)
|
||||||
|
{
|
||||||
|
connection.Value.Socket.SendAsync(
|
||||||
|
segment,
|
||||||
|
WebSocketMessageType.Binary,
|
||||||
|
true,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task HandlePacket(Account.Account currentUser, string deviceId, WebSocketPacket packet,
|
||||||
|
WebSocket socket)
|
||||||
|
{
|
||||||
|
if (_handlerMap.TryGetValue(packet.Type, out var handler))
|
||||||
|
{
|
||||||
|
await handler.HandleAsync(currentUser, deviceId, packet, socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await socket.SendAsync(
|
||||||
|
new ArraySegment<byte>(new WebSocketPacket
|
||||||
|
{
|
||||||
|
Type = WebSocketPacketType.Error,
|
||||||
|
ErrorMessage = $"Unprocessable packet: {packet.Type}"
|
||||||
|
}.ToBytes()),
|
||||||
|
WebSocketMessageType.Binary,
|
||||||
|
true,
|
||||||
|
CancellationToken.None
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,8 +11,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
<PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
|
<PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
|
||||||
<PackageReference Include="Casbin.NET" Version="2.12.0" />
|
|
||||||
<PackageReference Include="Casbin.NET.Adapter.EFCore" Version="2.5.0" />
|
|
||||||
<PackageReference Include="CorePush" Version="4.3.0" />
|
<PackageReference Include="CorePush" Version="4.3.0" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
|
2354
DysonNetwork.Sphere/Migrations/20250502123707_AddChatMessage.Designer.cs
generated
Normal file
2354
DysonNetwork.Sphere/Migrations/20250502123707_AddChatMessage.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
238
DysonNetwork.Sphere/Migrations/20250502123707_AddChatMessage.cs
Normal file
238
DysonNetwork.Sphere/Migrations/20250502123707_AddChatMessage.cs
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddChatMessage : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "pk_chat_members",
|
||||||
|
table: "chat_members");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "message_id",
|
||||||
|
table: "files",
|
||||||
|
type: "uuid",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "id",
|
||||||
|
table: "chat_members",
|
||||||
|
type: "uuid",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||||
|
|
||||||
|
migrationBuilder.AddUniqueConstraint(
|
||||||
|
name: "ak_chat_members_chat_room_id_account_id",
|
||||||
|
table: "chat_members",
|
||||||
|
columns: new[] { "chat_room_id", "account_id" });
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "pk_chat_members",
|
||||||
|
table: "chat_members",
|
||||||
|
column: "id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "chat_messages",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||||
|
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||||
|
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||||
|
members_metioned = table.Column<List<Guid>>(type: "jsonb", nullable: true),
|
||||||
|
edited_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||||
|
replied_message_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
forwarded_message_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
chat_room_id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_chat_messages", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_messages_chat_members_sender_id",
|
||||||
|
column: x => x.sender_id,
|
||||||
|
principalTable: "chat_members",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_messages_chat_messages_forwarded_message_id",
|
||||||
|
column: x => x.forwarded_message_id,
|
||||||
|
principalTable: "chat_messages",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_messages_chat_messages_replied_message_id",
|
||||||
|
column: x => x.replied_message_id,
|
||||||
|
principalTable: "chat_messages",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_messages_chat_rooms_chat_room_id",
|
||||||
|
column: x => x.chat_room_id,
|
||||||
|
principalTable: "chat_rooms",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "chat_statuses",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
message_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
read_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_chat_statuses", x => new { x.message_id, x.sender_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_statuses_chat_members_sender_id",
|
||||||
|
column: x => x.sender_id,
|
||||||
|
principalTable: "chat_members",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_chat_statuses_chat_messages_message_id",
|
||||||
|
column: x => x.message_id,
|
||||||
|
principalTable: "chat_messages",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "message_reaction",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
message_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
symbol = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
attitude = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_message_reaction", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_message_reaction_chat_members_sender_id",
|
||||||
|
column: x => x.sender_id,
|
||||||
|
principalTable: "chat_members",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_message_reaction_chat_messages_message_id",
|
||||||
|
column: x => x.message_id,
|
||||||
|
principalTable: "chat_messages",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_files_message_id",
|
||||||
|
table: "files",
|
||||||
|
column: "message_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_messages_chat_room_id",
|
||||||
|
table: "chat_messages",
|
||||||
|
column: "chat_room_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_messages_forwarded_message_id",
|
||||||
|
table: "chat_messages",
|
||||||
|
column: "forwarded_message_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_messages_replied_message_id",
|
||||||
|
table: "chat_messages",
|
||||||
|
column: "replied_message_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_messages_sender_id",
|
||||||
|
table: "chat_messages",
|
||||||
|
column: "sender_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_chat_statuses_sender_id",
|
||||||
|
table: "chat_statuses",
|
||||||
|
column: "sender_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_message_reaction_message_id",
|
||||||
|
table: "message_reaction",
|
||||||
|
column: "message_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_message_reaction_sender_id",
|
||||||
|
table: "message_reaction",
|
||||||
|
column: "sender_id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "fk_files_chat_messages_message_id",
|
||||||
|
table: "files",
|
||||||
|
column: "message_id",
|
||||||
|
principalTable: "chat_messages",
|
||||||
|
principalColumn: "id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "fk_files_chat_messages_message_id",
|
||||||
|
table: "files");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "chat_statuses");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "message_reaction");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "chat_messages");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "ix_files_message_id",
|
||||||
|
table: "files");
|
||||||
|
|
||||||
|
migrationBuilder.DropUniqueConstraint(
|
||||||
|
name: "ak_chat_members_chat_room_id_account_id",
|
||||||
|
table: "chat_members");
|
||||||
|
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "pk_chat_members",
|
||||||
|
table: "chat_members");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "message_id",
|
||||||
|
table: "files");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "id",
|
||||||
|
table: "chat_members");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "pk_chat_members",
|
||||||
|
table: "chat_members",
|
||||||
|
columns: new[] { "chat_room_id", "account_id" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2359
DysonNetwork.Sphere/Migrations/20250502174318_DontKnowHowToNameThing.Designer.cs
generated
Normal file
2359
DysonNetwork.Sphere/Migrations/20250502174318_DontKnowHowToNameThing.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,62 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NpgsqlTypes;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class DontKnowHowToNameThing : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<NpgsqlTsVector>(
|
||||||
|
name: "search_vector",
|
||||||
|
table: "posts",
|
||||||
|
type: "tsvector",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(NpgsqlTsVector),
|
||||||
|
oldType: "tsvector")
|
||||||
|
.OldAnnotation("Npgsql:TsVectorConfig", "simple")
|
||||||
|
.OldAnnotation("Npgsql:TsVectorProperties", new[] { "title", "description", "content" });
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "nick",
|
||||||
|
table: "chat_members",
|
||||||
|
type: "character varying(1024)",
|
||||||
|
maxLength: 1024,
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "notify",
|
||||||
|
table: "chat_members",
|
||||||
|
type: "integer",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "nick",
|
||||||
|
table: "chat_members");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "notify",
|
||||||
|
table: "chat_members");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<NpgsqlTsVector>(
|
||||||
|
name: "search_vector",
|
||||||
|
table: "posts",
|
||||||
|
type: "tsvector",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(NpgsqlTsVector),
|
||||||
|
oldType: "tsvector",
|
||||||
|
oldNullable: true)
|
||||||
|
.Annotation("Npgsql:TsVectorConfig", "simple")
|
||||||
|
.Annotation("Npgsql:TsVectorProperties", new[] { "title", "description", "content" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -647,14 +647,19 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.ChatMember", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("ChatRoomId")
|
b.Property<Guid>("Id")
|
||||||
.HasColumnType("bigint")
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnName("chat_room_id");
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
b.Property<long>("AccountId")
|
b.Property<long>("AccountId")
|
||||||
.HasColumnType("bigint")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("account_id");
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<long>("ChatRoomId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("chat_room_id");
|
||||||
|
|
||||||
b.Property<Instant>("CreatedAt")
|
b.Property<Instant>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("created_at");
|
.HasColumnName("created_at");
|
||||||
@ -671,6 +676,15 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("joined_at");
|
.HasColumnName("joined_at");
|
||||||
|
|
||||||
|
b.Property<string>("Nick")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("nick");
|
||||||
|
|
||||||
|
b.Property<int>("Notify")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("notify");
|
||||||
|
|
||||||
b.Property<int>("Role")
|
b.Property<int>("Role")
|
||||||
.HasColumnType("integer")
|
.HasColumnType("integer")
|
||||||
.HasColumnName("role");
|
.HasColumnName("role");
|
||||||
@ -679,9 +693,12 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("updated_at");
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
b.HasKey("ChatRoomId", "AccountId")
|
b.HasKey("Id")
|
||||||
.HasName("pk_chat_members");
|
.HasName("pk_chat_members");
|
||||||
|
|
||||||
|
b.HasAlternateKey("ChatRoomId", "AccountId")
|
||||||
|
.HasName("ak_chat_members_chat_room_id_account_id");
|
||||||
|
|
||||||
b.HasIndex("AccountId")
|
b.HasIndex("AccountId")
|
||||||
.HasDatabaseName("ix_chat_members_account_id");
|
.HasDatabaseName("ix_chat_members_account_id");
|
||||||
|
|
||||||
@ -756,6 +773,167 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("chat_rooms", (string)null);
|
b.ToTable("chat_rooms", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<long>("ChatRoomId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("chat_room_id");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(4096)
|
||||||
|
.HasColumnType("character varying(4096)")
|
||||||
|
.HasColumnName("content");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("EditedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("edited_at");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ForwardedMessageId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("forwarded_message_id");
|
||||||
|
|
||||||
|
b.Property<List<Guid>>("MembersMetioned")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("members_metioned");
|
||||||
|
|
||||||
|
b.Property<Dictionary<string, object>>("Meta")
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.HasColumnName("meta");
|
||||||
|
|
||||||
|
b.Property<Guid?>("RepliedMessageId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("replied_message_id");
|
||||||
|
|
||||||
|
b.Property<Guid>("SenderId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("sender_id");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)")
|
||||||
|
.HasColumnName("type");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_chat_messages");
|
||||||
|
|
||||||
|
b.HasIndex("ChatRoomId")
|
||||||
|
.HasDatabaseName("ix_chat_messages_chat_room_id");
|
||||||
|
|
||||||
|
b.HasIndex("ForwardedMessageId")
|
||||||
|
.HasDatabaseName("ix_chat_messages_forwarded_message_id");
|
||||||
|
|
||||||
|
b.HasIndex("RepliedMessageId")
|
||||||
|
.HasDatabaseName("ix_chat_messages_replied_message_id");
|
||||||
|
|
||||||
|
b.HasIndex("SenderId")
|
||||||
|
.HasDatabaseName("ix_chat_messages_sender_id");
|
||||||
|
|
||||||
|
b.ToTable("chat_messages", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<int>("Attitude")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("attitude");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Guid>("MessageId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("message_id");
|
||||||
|
|
||||||
|
b.Property<Guid>("SenderId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("sender_id");
|
||||||
|
|
||||||
|
b.Property<string>("Symbol")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)")
|
||||||
|
.HasColumnName("symbol");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_message_reaction");
|
||||||
|
|
||||||
|
b.HasIndex("MessageId")
|
||||||
|
.HasDatabaseName("ix_message_reaction_message_id");
|
||||||
|
|
||||||
|
b.HasIndex("SenderId")
|
||||||
|
.HasDatabaseName("ix_message_reaction_sender_id");
|
||||||
|
|
||||||
|
b.ToTable("message_reaction", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageStatus", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("MessageId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("message_id");
|
||||||
|
|
||||||
|
b.Property<Guid>("SenderId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("sender_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Instant>("ReadAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("read_at");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("MessageId", "SenderId")
|
||||||
|
.HasName("pk_chat_statuses");
|
||||||
|
|
||||||
|
b.HasIndex("SenderId")
|
||||||
|
.HasDatabaseName("ix_chat_statuses_sender_id");
|
||||||
|
|
||||||
|
b.ToTable("chat_statuses", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -950,12 +1128,8 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnName("replied_post_id");
|
.HasColumnName("replied_post_id");
|
||||||
|
|
||||||
b.Property<NpgsqlTsVector>("SearchVector")
|
b.Property<NpgsqlTsVector>("SearchVector")
|
||||||
.IsRequired()
|
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
|
||||||
.HasColumnType("tsvector")
|
.HasColumnType("tsvector")
|
||||||
.HasColumnName("search_vector")
|
.HasColumnName("search_vector");
|
||||||
.HasAnnotation("Npgsql:TsVectorConfig", "simple")
|
|
||||||
.HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" });
|
|
||||||
|
|
||||||
b.Property<long?>("ThreadedPostId")
|
b.Property<long?>("ThreadedPostId")
|
||||||
.HasColumnType("bigint")
|
.HasColumnType("bigint")
|
||||||
@ -1470,6 +1644,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("character varying(256)")
|
.HasColumnType("character varying(256)")
|
||||||
.HasColumnName("hash");
|
.HasColumnName("hash");
|
||||||
|
|
||||||
|
b.Property<Guid?>("MessageId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("message_id");
|
||||||
|
|
||||||
b.Property<string>("MimeType")
|
b.Property<string>("MimeType")
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)")
|
.HasColumnType("character varying(256)")
|
||||||
@ -1516,6 +1694,9 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.HasIndex("AccountId")
|
b.HasIndex("AccountId")
|
||||||
.HasDatabaseName("ix_files_account_id");
|
.HasDatabaseName("ix_files_account_id");
|
||||||
|
|
||||||
|
b.HasIndex("MessageId")
|
||||||
|
.HasDatabaseName("ix_files_message_id");
|
||||||
|
|
||||||
b.HasIndex("PostId")
|
b.HasIndex("PostId")
|
||||||
.HasDatabaseName("ix_files_post_id");
|
.HasDatabaseName("ix_files_post_id");
|
||||||
|
|
||||||
@ -1774,6 +1955,85 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Realm");
|
b.Navigation("Realm");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.ChatRoom", "ChatRoom")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ChatRoomId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.Message", "ForwardedMessage")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ForwardedMessageId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.Message", "RepliedMessage")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RepliedMessageId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.HasConstraintName("fk_chat_messages_chat_messages_replied_message_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SenderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_chat_messages_chat_members_sender_id");
|
||||||
|
|
||||||
|
b.Navigation("ChatRoom");
|
||||||
|
|
||||||
|
b.Navigation("ForwardedMessage");
|
||||||
|
|
||||||
|
b.Navigation("RepliedMessage");
|
||||||
|
|
||||||
|
b.Navigation("Sender");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageReaction", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message")
|
||||||
|
.WithMany("Reactions")
|
||||||
|
.HasForeignKey("MessageId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_message_reaction_chat_messages_message_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SenderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_message_reaction_chat_members_sender_id");
|
||||||
|
|
||||||
|
b.Navigation("Message");
|
||||||
|
|
||||||
|
b.Navigation("Sender");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.MessageStatus", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.Message", "Message")
|
||||||
|
.WithMany("Statuses")
|
||||||
|
.HasForeignKey("MessageId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_chat_statuses_chat_messages_message_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.ChatMember", "Sender")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SenderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_chat_statuses_chat_members_sender_id");
|
||||||
|
|
||||||
|
b.Navigation("Message");
|
||||||
|
|
||||||
|
b.Navigation("Sender");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
||||||
@ -1965,6 +2225,11 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasConstraintName("fk_files_accounts_account_id");
|
.HasConstraintName("fk_files_accounts_account_id");
|
||||||
|
|
||||||
|
b.HasOne("DysonNetwork.Sphere.Chat.Message", null)
|
||||||
|
.WithMany("Attachments")
|
||||||
|
.HasForeignKey("MessageId")
|
||||||
|
.HasConstraintName("fk_files_chat_messages_message_id");
|
||||||
|
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.Post", null)
|
b.HasOne("DysonNetwork.Sphere.Post.Post", null)
|
||||||
.WithMany("Attachments")
|
.WithMany("Attachments")
|
||||||
.HasForeignKey("PostId")
|
.HasForeignKey("PostId")
|
||||||
@ -2047,6 +2312,15 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Members");
|
b.Navigation("Members");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.Chat.Message", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Attachments");
|
||||||
|
|
||||||
|
b.Navigation("Reactions");
|
||||||
|
|
||||||
|
b.Navigation("Statuses");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Members");
|
b.Navigation("Members");
|
||||||
|
@ -51,7 +51,7 @@ public class Post : ModelBase
|
|||||||
public Post? ForwardedPost { get; set; }
|
public Post? ForwardedPost { get; set; }
|
||||||
public ICollection<CloudFile> Attachments { get; set; } = new List<CloudFile>();
|
public ICollection<CloudFile> Attachments { get; set; } = new List<CloudFile>();
|
||||||
|
|
||||||
[JsonIgnore] public NpgsqlTsVector SearchVector { get; set; }
|
[JsonIgnore] public NpgsqlTsVector? SearchVector { get; set; }
|
||||||
|
|
||||||
public Publisher Publisher { get; set; } = null!;
|
public Publisher Publisher { get; set; } = null!;
|
||||||
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Casbin;
|
|
||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -48,6 +48,6 @@ public class PublisherMember : ModelBase
|
|||||||
public long AccountId { get; set; }
|
public long AccountId { get; set; }
|
||||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
|
|
||||||
public PublisherMemberRole Role { get; set; }
|
public PublisherMemberRole Role { get; set; } = PublisherMemberRole.Viewer;
|
||||||
public Instant? JoinedAt { get; set; }
|
public Instant? JoinedAt { get; set; }
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Casbin;
|
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
@ -3,19 +3,17 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using Casbin;
|
|
||||||
using Casbin.Persist.Adapter.EFCore;
|
|
||||||
using DysonNetwork.Sphere;
|
using DysonNetwork.Sphere;
|
||||||
using DysonNetwork.Sphere.Account;
|
using DysonNetwork.Sphere.Account;
|
||||||
using DysonNetwork.Sphere.Activity;
|
using DysonNetwork.Sphere.Activity;
|
||||||
using DysonNetwork.Sphere.Auth;
|
using DysonNetwork.Sphere.Auth;
|
||||||
using DysonNetwork.Sphere.Chat;
|
using DysonNetwork.Sphere.Chat;
|
||||||
using DysonNetwork.Sphere.Connection;
|
using DysonNetwork.Sphere.Connection;
|
||||||
|
using DysonNetwork.Sphere.Connection.Handlers;
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Realm;
|
using DysonNetwork.Sphere.Realm;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.RateLimiting;
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
@ -118,6 +116,10 @@ builder.Services.AddSwaggerGen(options =>
|
|||||||
});
|
});
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
|
|
||||||
|
// The handlers for websocket
|
||||||
|
builder.Services.AddScoped<IWebSocketPacketHandler, MessageReadHandler>();
|
||||||
|
|
||||||
|
// Services
|
||||||
builder.Services.AddScoped<WebSocketService>();
|
builder.Services.AddScoped<WebSocketService>();
|
||||||
builder.Services.AddScoped<EmailService>();
|
builder.Services.AddScoped<EmailService>();
|
||||||
builder.Services.AddScoped<PermissionService>();
|
builder.Services.AddScoped<PermissionService>();
|
||||||
@ -132,6 +134,7 @@ builder.Services.AddScoped<ActivityService>();
|
|||||||
builder.Services.AddScoped<PostService>();
|
builder.Services.AddScoped<PostService>();
|
||||||
builder.Services.AddScoped<RealmService>();
|
builder.Services.AddScoped<RealmService>();
|
||||||
builder.Services.AddScoped<ChatRoomService>();
|
builder.Services.AddScoped<ChatRoomService>();
|
||||||
|
builder.Services.AddScoped<ChatService>();
|
||||||
|
|
||||||
// Timed task
|
// Timed task
|
||||||
|
|
||||||
@ -213,7 +216,7 @@ app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
|
|||||||
}
|
}
|
||||||
|
|
||||||
var httpContext = eventContext.HttpContext;
|
var httpContext = eventContext.HttpContext;
|
||||||
var user = httpContext.Items["CurrentUser"] as Account;
|
if (httpContext.Items["CurrentUser"] is Account user)
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
eventContext.FailRequest(HttpStatusCode.Unauthorized);
|
eventContext.FailRequest(HttpStatusCode.Unauthorized);
|
||||||
|
@ -43,6 +43,6 @@ public class RealmMember : ModelBase
|
|||||||
public long AccountId { get; set; }
|
public long AccountId { get; set; }
|
||||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||||
|
|
||||||
public RealmMemberRole Role { get; set; }
|
public RealmMemberRole Role { get; set; } = RealmMemberRole.Normal;
|
||||||
public Instant? JoinedAt { get; set; }
|
public Instant? JoinedAt { get; set; }
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user