Compare commits
2 Commits
17de9a0f23
...
f6acb3f2f0
Author | SHA1 | Date | |
---|---|---|---|
f6acb3f2f0 | |||
46054dfb7b |
@ -1,8 +1,6 @@
|
||||
using Casbin;
|
||||
using DysonNetwork.Sphere.Permission;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Account;
|
||||
|
||||
|
@ -134,7 +134,6 @@ public class AppDatabase(
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Post.Post>()
|
||||
.HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content })
|
||||
.HasIndex(p => p.SearchVector)
|
||||
.HasMethod("GIN");
|
||||
modelBuilder.Entity<Post.Post>()
|
||||
@ -193,6 +192,16 @@ public class AppDatabase(
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<Chat.MessageStatus>()
|
||||
.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
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
|
@ -2,8 +2,6 @@ using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using Casbin;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
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.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
[ApiController]
|
||||
[Route("/chat")]
|
||||
public class ChatController : ControllerBase
|
||||
public partial class ChatController(AppDatabase db, ChatService cs) : ControllerBase
|
||||
{
|
||||
public class MarkMessageReadRequest
|
||||
{
|
||||
public Guid MessageId { 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
|
||||
}
|
||||
|
||||
public enum ChatMemberNotify
|
||||
{
|
||||
All,
|
||||
Mentions,
|
||||
None
|
||||
}
|
||||
|
||||
public class ChatMember : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
@ -43,7 +50,10 @@ public class ChatMember : ModelBase
|
||||
public long AccountId { get; set; }
|
||||
[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 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.Picture)
|
||||
.Include(e => e.ChatRoom.Background)
|
||||
.Include(e => e.ChatRoom.Type == ChatRoomType.DirectMessage ? e.ChatRoom.Members : null)
|
||||
.Select(m => m.ChatRoom)
|
||||
.ToListAsync();
|
||||
|
||||
|
@ -1,9 +1,47 @@
|
||||
using DysonNetwork.Sphere;
|
||||
using DysonNetwork.Sphere.Chat;
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using DysonNetwork.Sphere.Connection;
|
||||
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)
|
||||
{
|
||||
var existingStatus = await db.ChatStatuses
|
||||
@ -45,4 +83,48 @@ public class ChatService(AppDatabase db)
|
||||
|
||||
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.Schema;
|
||||
using System.Net.Mail;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
@ -15,7 +15,7 @@ public class Message : ModelBase
|
||||
[Column(TypeName = "jsonb")] public List<Guid>? MembersMetioned { 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<MessageStatus> Statuses { get; set; } = new List<MessageStatus>();
|
||||
|
||||
@ -39,6 +39,7 @@ public enum MessageReactionAttitude
|
||||
|
||||
public class MessageReaction : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid MessageId { get; set; }
|
||||
[JsonIgnore] public Message Message { get; set; } = null!;
|
||||
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.Net.WebSockets;
|
||||
using DysonNetwork.Sphere.Chat;
|
||||
|
||||
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),
|
||||
(WebSocket Socket, CancellationTokenSource Cts)
|
||||
> ActiveConnections = new();
|
||||
@ -17,7 +25,8 @@ public class WebSocketService(ChatService cs)
|
||||
)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
@ -33,40 +42,58 @@ public class WebSocketService(ChatService cs)
|
||||
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":
|
||||
var request = packet.GetData<ChatController.MarkMessageReadRequest>();
|
||||
if (request is null)
|
||||
{
|
||||
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
|
||||
);
|
||||
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;
|
||||
connection.Value.Socket.SendAsync(
|
||||
segment,
|
||||
WebSocketMessageType.Binary,
|
||||
true,
|
||||
CancellationToken.None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<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="EFCore.NamingConventions" Version="9.0.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 =>
|
||||
{
|
||||
b.Property<long>("ChatRoomId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("chat_room_id");
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<long>("AccountId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<long>("ChatRoomId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("chat_room_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
@ -671,6 +676,15 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.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")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("role");
|
||||
@ -679,9 +693,12 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("ChatRoomId", "AccountId")
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_members");
|
||||
|
||||
b.HasAlternateKey("ChatRoomId", "AccountId")
|
||||
.HasName("ak_chat_members_chat_room_id_account_id");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_chat_members_account_id");
|
||||
|
||||
@ -756,6 +773,167 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -950,12 +1128,8 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnName("replied_post_id");
|
||||
|
||||
b.Property<NpgsqlTsVector>("SearchVector")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("tsvector")
|
||||
.HasColumnName("search_vector")
|
||||
.HasAnnotation("Npgsql:TsVectorConfig", "simple")
|
||||
.HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" });
|
||||
.HasColumnName("search_vector");
|
||||
|
||||
b.Property<long?>("ThreadedPostId")
|
||||
.HasColumnType("bigint")
|
||||
@ -1470,6 +1644,10 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<Guid?>("MessageId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("message_id");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
@ -1516,6 +1694,9 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_files_account_id");
|
||||
|
||||
b.HasIndex("MessageId")
|
||||
.HasDatabaseName("ix_files_message_id");
|
||||
|
||||
b.HasIndex("PostId")
|
||||
.HasDatabaseName("ix_files_post_id");
|
||||
|
||||
@ -1774,6 +1955,85 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
|
||||
@ -1965,6 +2225,11 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.IsRequired()
|
||||
.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)
|
||||
.WithMany("Attachments")
|
||||
.HasForeignKey("PostId")
|
||||
@ -2047,6 +2312,15 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
|
@ -51,7 +51,7 @@ public class Post : ModelBase
|
||||
public Post? ForwardedPost { get; set; }
|
||||
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 ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
using Casbin;
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using DysonNetwork.Sphere.Permission;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -74,6 +74,9 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
||||
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.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.");
|
||||
}
|
||||
|
||||
if (post.Empty)
|
||||
throw new InvalidOperationException("Cannot create a post with barely no content.");
|
||||
// Vectorize the quill delta 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
|
||||
|
||||
@ -140,6 +162,9 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
||||
Instant? publishedAt = null
|
||||
)
|
||||
{
|
||||
if (post.Empty)
|
||||
throw new InvalidOperationException("Cannot edit a post to barely no content.");
|
||||
|
||||
post.EditedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
if (post.Empty)
|
||||
throw new InvalidOperationException("Cannot edit a post to barely no content.");
|
||||
// Vectorize the quill delta 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);
|
||||
await db.SaveChangesAsync();
|
||||
|
@ -48,6 +48,6 @@ public class PublisherMember : ModelBase
|
||||
public long AccountId { get; set; }
|
||||
[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; }
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Casbin;
|
||||
using DysonNetwork.Sphere.Permission;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -3,19 +3,17 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.RateLimiting;
|
||||
using Casbin;
|
||||
using Casbin.Persist.Adapter.EFCore;
|
||||
using DysonNetwork.Sphere;
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using DysonNetwork.Sphere.Activity;
|
||||
using DysonNetwork.Sphere.Auth;
|
||||
using DysonNetwork.Sphere.Chat;
|
||||
using DysonNetwork.Sphere.Connection;
|
||||
using DysonNetwork.Sphere.Connection.Handlers;
|
||||
using DysonNetwork.Sphere.Permission;
|
||||
using DysonNetwork.Sphere.Post;
|
||||
using DysonNetwork.Sphere.Realm;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
@ -118,6 +116,10 @@ builder.Services.AddSwaggerGen(options =>
|
||||
});
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
// The handlers for websocket
|
||||
builder.Services.AddScoped<IWebSocketPacketHandler, MessageReadHandler>();
|
||||
|
||||
// Services
|
||||
builder.Services.AddScoped<WebSocketService>();
|
||||
builder.Services.AddScoped<EmailService>();
|
||||
builder.Services.AddScoped<PermissionService>();
|
||||
@ -132,6 +134,7 @@ builder.Services.AddScoped<ActivityService>();
|
||||
builder.Services.AddScoped<PostService>();
|
||||
builder.Services.AddScoped<RealmService>();
|
||||
builder.Services.AddScoped<ChatRoomService>();
|
||||
builder.Services.AddScoped<ChatService>();
|
||||
|
||||
// Timed task
|
||||
|
||||
@ -213,7 +216,7 @@ app.MapTus("/files/tus", _ => Task.FromResult<DefaultTusConfiguration>(new()
|
||||
}
|
||||
|
||||
var httpContext = eventContext.HttpContext;
|
||||
var user = httpContext.Items["CurrentUser"] as Account;
|
||||
if (httpContext.Items["CurrentUser"] is Account user)
|
||||
if (user is null)
|
||||
{
|
||||
eventContext.FailRequest(HttpStatusCode.Unauthorized);
|
||||
|
@ -43,6 +43,6 @@ public class RealmMember : ModelBase
|
||||
public long AccountId { get; set; }
|
||||
[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; }
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user