Compare commits
2 Commits
17de9a0f23
...
f6acb3f2f0
Author | SHA1 | Date | |
---|---|---|---|
f6acb3f2f0 | |||
46054dfb7b |
@ -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;
|
||||||
|
|
||||||
|
@ -134,7 +134,6 @@ public class AppDatabase(
|
|||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
modelBuilder.Entity<Post.Post>()
|
modelBuilder.Entity<Post.Post>()
|
||||||
.HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content })
|
|
||||||
.HasIndex(p => p.SearchVector)
|
.HasIndex(p => p.SearchVector)
|
||||||
.HasMethod("GIN");
|
.HasMethod("GIN");
|
||||||
modelBuilder.Entity<Post.Post>()
|
modelBuilder.Entity<Post.Post>()
|
||||||
@ -193,6 +192,16 @@ public class AppDatabase(
|
|||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Chat.MessageStatus>()
|
modelBuilder.Entity<Chat.MessageStatus>()
|
||||||
.HasKey(e => new { e.MessageId, e.SenderId });
|
.HasKey(e => new { e.MessageId, e.SenderId });
|
||||||
|
modelBuilder.Entity<Chat.Message>()
|
||||||
|
.HasOne(m => m.ForwardedMessage)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(m => m.ForwardedMessageId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
modelBuilder.Entity<Chat.Message>()
|
||||||
|
.HasOne(m => m.RepliedMessage)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(m => m.RepliedMessageId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
// Automatically apply soft-delete filter to all entities inheriting BaseModel
|
// Automatically apply soft-delete filter to all entities inheriting BaseModel
|
||||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||||
|
@ -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;
|
||||||
|
@ -74,6 +74,9 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
|||||||
List<string>? categories = null
|
List<string>? categories = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if (post.Empty)
|
||||||
|
throw new InvalidOperationException("Cannot create a post with barely no content.");
|
||||||
|
|
||||||
if (post.PublishedAt is not null)
|
if (post.PublishedAt is not null)
|
||||||
{
|
{
|
||||||
if (post.PublishedAt.Value.ToDateTimeUtc() < DateTime.UtcNow)
|
if (post.PublishedAt.Value.ToDateTimeUtc() < DateTime.UtcNow)
|
||||||
@ -118,8 +121,27 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
|||||||
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.Empty)
|
// Vectorize the quill delta content
|
||||||
throw new InvalidOperationException("Cannot create a post with barely no content.");
|
if (post.Content?.RootElement is { ValueKind: JsonValueKind.Array })
|
||||||
|
{
|
||||||
|
var searchTextBuilder = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(post.Title))
|
||||||
|
searchTextBuilder.AppendLine(post.Title);
|
||||||
|
if (!string.IsNullOrWhiteSpace(post.Description))
|
||||||
|
searchTextBuilder.AppendLine(post.Description);
|
||||||
|
|
||||||
|
foreach (var element in post.Content.RootElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (element is { ValueKind: JsonValueKind.Object } &&
|
||||||
|
element.TryGetProperty("insert", out var insertProperty) &&
|
||||||
|
insertProperty.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
searchTextBuilder.Append(insertProperty.GetString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post.SearchVector = EF.Functions.ToTsVector(searchTextBuilder.ToString().Trim());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Notify the subscribers
|
// TODO Notify the subscribers
|
||||||
|
|
||||||
@ -140,6 +162,9 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
|||||||
Instant? publishedAt = null
|
Instant? publishedAt = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if (post.Empty)
|
||||||
|
throw new InvalidOperationException("Cannot edit a post to barely no content.");
|
||||||
|
|
||||||
post.EditedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
post.EditedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||||
|
|
||||||
if (publishedAt is not null)
|
if (publishedAt is not null)
|
||||||
@ -196,8 +221,27 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
|||||||
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (post.Empty)
|
// Vectorize the quill delta content
|
||||||
throw new InvalidOperationException("Cannot edit a post to barely no content.");
|
if (post.Content?.RootElement is { ValueKind: JsonValueKind.Array })
|
||||||
|
{
|
||||||
|
var searchTextBuilder = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(post.Title))
|
||||||
|
searchTextBuilder.AppendLine(post.Title);
|
||||||
|
if (!string.IsNullOrWhiteSpace(post.Description))
|
||||||
|
searchTextBuilder.AppendLine(post.Description);
|
||||||
|
|
||||||
|
foreach (var element in post.Content.RootElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (element is { ValueKind: JsonValueKind.Object } &&
|
||||||
|
element.TryGetProperty("insert", out var insertProperty) &&
|
||||||
|
insertProperty.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
searchTextBuilder.Append(insertProperty.GetString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post.SearchVector = EF.Functions.ToTsVector(searchTextBuilder.ToString().Trim());
|
||||||
|
}
|
||||||
|
|
||||||
db.Update(post);
|
db.Update(post);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
@ -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