From 17de9a0f23b8fdecaca8744768fe5f35681105c9 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 2 May 2025 19:51:32 +0800 Subject: [PATCH] :sparkles: Add chat message handling and WebSocket integration Introduce new `ChatService` for managing chat messages, including marking messages as read and checking read status. Add `WebSocketPacket` class for handling WebSocket communication and integrate it with `WebSocketService` to process chat-related packets. Enhance `ChatRoom` and `ChatMember` models with additional fields and relationships. Update `AppDatabase` to include new chat-related entities and adjust permissions for chat creation. --- DysonNetwork.Sphere/AppDatabase.cs | 16 +- DysonNetwork.Sphere/Chat/ChatController.cs | 12 + DysonNetwork.Sphere/Chat/ChatRoom.cs | 1 + .../Chat/ChatRoomController.cs | 10 +- DysonNetwork.Sphere/Chat/ChatService.cs | 48 + DysonNetwork.Sphere/Chat/Message.cs | 67 + .../Connection/WebSocketController.cs | 14 +- .../Connection/WebSocketPacket.cs | 62 + .../Connection/WebSocketService.cs | 39 +- ...0250501080049_InitialMigration.Designer.cs | 1744 ----------------- .../Migrations/20250502040651_RealmAndChat.cs | 51 - ...250502041309_InitialMigration.Designer.cs} | 4 +- ....cs => 20250502041309_InitialMigration.cs} | 203 ++ DysonNetwork.Sphere/Post/PostService.cs | 2 +- DysonNetwork.Sphere/Program.cs | 4 + .../Realm/RealmChatController.cs | 2 +- DysonNetwork.Sphere/Realm/RealmController.cs | 9 +- DysonNetwork.Sphere/Storage/FileController.cs | 14 +- 18 files changed, 483 insertions(+), 1819 deletions(-) create mode 100644 DysonNetwork.Sphere/Chat/ChatService.cs create mode 100644 DysonNetwork.Sphere/Chat/Message.cs create mode 100644 DysonNetwork.Sphere/Connection/WebSocketPacket.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.Designer.cs delete mode 100644 DysonNetwork.Sphere/Migrations/20250502040651_RealmAndChat.cs rename DysonNetwork.Sphere/Migrations/{20250502040651_RealmAndChat.Designer.cs => 20250502041309_InitialMigration.Designer.cs} (99%) rename DysonNetwork.Sphere/Migrations/{20250501080049_InitialMigration.cs => 20250502041309_InitialMigration.cs} (83%) diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs index a23448e..6df65a4 100644 --- a/DysonNetwork.Sphere/AppDatabase.cs +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -50,9 +50,11 @@ public class AppDatabase( public DbSet Realms { get; set; } public DbSet RealmMembers { get; set; } - + public DbSet ChatRooms { get; set; } public DbSet ChatMembers { get; set; } + public DbSet ChatMessages { get; set; } + public DbSet ChatStatuses { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -80,7 +82,7 @@ public class AppDatabase( PermissionService.NewPermissionNode("group:default", "global", "posts.create", true), PermissionService.NewPermissionNode("group:default", "global", "publishers.create", true), PermissionService.NewPermissionNode("group:default", "global", "files.create", true), - PermissionService.NewPermissionNode("group:default", "global", "chatrooms.create", true) + PermissionService.NewPermissionNode("group:default", "global", "chat.create", true) } }); await context.SaveChangesAsync(cancellationToken); @@ -161,7 +163,7 @@ public class AppDatabase( .HasMany(p => p.Collections) .WithMany(c => c.Posts) .UsingEntity(j => j.ToTable("post_collection_links")); - + modelBuilder.Entity() .HasKey(pm => new { pm.RealmId, pm.AccountId }); modelBuilder.Entity() @@ -174,9 +176,11 @@ public class AppDatabase( .WithMany() .HasForeignKey(pm => pm.AccountId) .OnDelete(DeleteBehavior.Cascade); - + modelBuilder.Entity() - .HasKey(pm => new { pm.ChatRoomId, pm.AccountId }); + .HasKey(pm => new { pm.Id }); + modelBuilder.Entity() + .HasAlternateKey(pm => new { pm.ChatRoomId, pm.AccountId }); modelBuilder.Entity() .HasOne(pm => pm.ChatRoom) .WithMany(p => p.Members) @@ -187,6 +191,8 @@ public class AppDatabase( .WithMany() .HasForeignKey(pm => pm.AccountId) .OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity() + .HasKey(e => new { e.MessageId, e.SenderId }); // Automatically apply soft-delete filter to all entities inheriting BaseModel foreach (var entityType in modelBuilder.Model.GetEntityTypes()) diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs index e69de29..2c9781f 100644 --- a/DysonNetwork.Sphere/Chat/ChatController.cs +++ b/DysonNetwork.Sphere/Chat/ChatController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; + +[ApiController] +[Route("/chat")] +public class ChatController : ControllerBase +{ + public class MarkMessageReadRequest + { + public Guid MessageId { get; set; } + public long ChatRoomId { get; set; } + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/ChatRoom.cs b/DysonNetwork.Sphere/Chat/ChatRoom.cs index 35540bb..3f8bb56 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoom.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoom.cs @@ -37,6 +37,7 @@ public enum ChatMemberRole public class ChatMember : ModelBase { + public Guid Id { get; set; } public long ChatRoomId { get; set; } [JsonIgnore] public ChatRoom ChatRoom { get; set; } = null!; public long AccountId { get; set; } diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs b/DysonNetwork.Sphere/Chat/ChatRoomController.cs index 537b86c..6067e06 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs @@ -1,8 +1,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; +using DysonNetwork.Sphere.Permission; using DysonNetwork.Sphere.Realm; using DysonNetwork.Sphere.Storage; +using Microsoft.AspNetCore.Authorization; namespace DysonNetwork.Sphere.Chat; @@ -17,6 +19,7 @@ public class ChatRoomController(AppDatabase db, FileService fs) : ControllerBase .Where(c => c.Id == id) .Include(e => e.Picture) .Include(e => e.Background) + .Include(e => e.Realm) .FirstOrDefaultAsync(); if (chatRoom is null) return NotFound(); return Ok(chatRoom); @@ -51,6 +54,8 @@ public class ChatRoomController(AppDatabase db, FileService fs) : ControllerBase } [HttpPost] + [Authorize] + [RequiredPermission("global", "chat.create")] public async Task> CreateChatRoom(ChatRoomRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); @@ -65,7 +70,8 @@ public class ChatRoomController(AppDatabase db, FileService fs) : ControllerBase new() { Role = ChatMemberRole.Owner, - AccountId = currentUser.Id + AccountId = currentUser.Id, + JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) } } }; @@ -105,7 +111,7 @@ public class ChatRoomController(AppDatabase db, FileService fs) : ControllerBase return Ok(chatRoom); } - [HttpPut("{id:long}")] + [HttpPatch("{id:long}")] public async Task> UpdateChatRoom(long id, [FromBody] ChatRoomRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); diff --git a/DysonNetwork.Sphere/Chat/ChatService.cs b/DysonNetwork.Sphere/Chat/ChatService.cs new file mode 100644 index 0000000..9d037a4 --- /dev/null +++ b/DysonNetwork.Sphere/Chat/ChatService.cs @@ -0,0 +1,48 @@ +using DysonNetwork.Sphere; +using DysonNetwork.Sphere.Chat; +using Microsoft.EntityFrameworkCore; + +public class ChatService(AppDatabase db) +{ + public async Task MarkMessageAsReadAsync(Guid messageId, long roomId, long userId) + { + var existingStatus = await db.ChatStatuses + .FirstOrDefaultAsync(x => x.MessageId == messageId && x.Sender.AccountId == userId); + var sender = await db.ChatMembers + .Where(m => m.AccountId == userId && m.ChatRoomId == roomId) + .FirstOrDefaultAsync(); + if (sender is null) throw new ArgumentException("User is not a member of the chat room."); + + if (existingStatus == null) + { + existingStatus = new MessageStatus + { + MessageId = messageId, + SenderId = sender.Id, + }; + db.ChatStatuses.Add(existingStatus); + } + + await db.SaveChangesAsync(); + } + + public async Task GetMessageReadStatus(Guid messageId, long userId) + { + return await db.ChatStatuses + .AnyAsync(x => x.MessageId == messageId && x.Sender.AccountId == userId); + } + + public async Task CountUnreadMessage(long userId, long chatRoomId) + { + var messages = await db.ChatMessages + .Where(m => m.ChatRoomId == chatRoomId) + .Select(m => new MessageStatusResponse + { + MessageId = m.Id, + IsRead = m.Statuses.Any(rs => rs.Sender.AccountId == userId) + }) + .ToListAsync(); + + return messages.Count(m => !m.IsRead); + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Chat/Message.cs b/DysonNetwork.Sphere/Chat/Message.cs new file mode 100644 index 0000000..3c41def --- /dev/null +++ b/DysonNetwork.Sphere/Chat/Message.cs @@ -0,0 +1,67 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Net.Mail; +using System.Text.Json.Serialization; +using NodaTime; + +namespace DysonNetwork.Sphere.Chat; + +public class Message : ModelBase +{ + public Guid Id { get; set; } = Guid.NewGuid(); + [MaxLength(1024)] public string Type { get; set; } = null!; + [MaxLength(4096)] public string Content { get; set; } = string.Empty; + [Column(TypeName = "jsonb")] public Dictionary? Meta { get; set; } + [Column(TypeName = "jsonb")] public List? MembersMetioned { get; set; } + public Instant? EditedAt { get; set; } + + public ICollection Attachments { get; set; } = new List(); + public ICollection Reactions { get; set; } = new List(); + public ICollection Statuses { get; set; } = new List(); + + public Guid? RepliedMessageId { get; set; } + public Message? RepliedMessage { get; set; } + public Guid? ForwardedMessageId { get; set; } + public Message? ForwardedMessage { get; set; } + + public Guid SenderId { get; set; } + public ChatMember Sender { get; set; } = null!; + public long ChatRoomId { get; set; } + [JsonIgnore] public ChatRoom ChatRoom { get; set; } = null!; +} + +public enum MessageReactionAttitude +{ + Positive, + Neutral, + Negative, +} + +public class MessageReaction : ModelBase +{ + public Guid MessageId { get; set; } + [JsonIgnore] public Message Message { get; set; } = null!; + public Guid SenderId { get; set; } + public ChatMember Sender { get; set; } = null!; + + [MaxLength(256)] public string Symbol { get; set; } = null!; + public MessageReactionAttitude Attitude { get; set; } +} + +/// If the status is exist, means the user has read the message. +public class MessageStatus : ModelBase +{ + public Guid MessageId { get; set; } + public Message Message { get; set; } = null!; + public Guid SenderId { get; set; } + public ChatMember Sender { get; set; } = null!; + + public Instant ReadAt { get; set; } +} + +[NotMapped] +public class MessageStatusResponse +{ + public Guid MessageId { get; set; } + public bool IsRead { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/WebSocketController.cs b/DysonNetwork.Sphere/Connection/WebSocketController.cs index 6db5ac6..62ccb1a 100644 --- a/DysonNetwork.Sphere/Connection/WebSocketController.cs +++ b/DysonNetwork.Sphere/Connection/WebSocketController.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Net.WebSockets; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Swashbuckle.AspNetCore.Annotations; namespace DysonNetwork.Sphere.Connection; @@ -52,7 +53,7 @@ public class WebSocketController(WebSocketService ws, ILogger try { - await _ConnectionEventLoop(connectionKey, webSocket, cts.Token); + await _ConnectionEventLoop(deviceId, currentUser, webSocket, cts.Token); } catch (Exception ex) { @@ -67,11 +68,14 @@ public class WebSocketController(WebSocketService ws, ILogger } private async Task _ConnectionEventLoop( - (long AccountId, string DeviceId) connectionKey, + string deviceId, + Account.Account currentUser, WebSocket webSocket, CancellationToken cancellationToken ) { + var connectionKey = (AccountId: currentUser.Id, DeviceId: deviceId); + var buffer = new byte[1024 * 4]; try { @@ -85,9 +89,11 @@ public class WebSocketController(WebSocketService ws, ILogger new ArraySegment(buffer), cancellationToken ); - } - // TODO handle values + var packet = WebSocketPacket.FromBytes(buffer[..receiveResult.Count]); + if (packet is null) continue; + ws.HandlePacket(currentUser, connectionKey.DeviceId, packet, webSocket); + } } catch (OperationCanceledException) { diff --git a/DysonNetwork.Sphere/Connection/WebSocketPacket.cs b/DysonNetwork.Sphere/Connection/WebSocketPacket.cs new file mode 100644 index 0000000..d079e64 --- /dev/null +++ b/DysonNetwork.Sphere/Connection/WebSocketPacket.cs @@ -0,0 +1,62 @@ +using System.Text.Json; + +public class WebSocketPacketType +{ + public const string Error = "error"; +} + +public class WebSocketPacket +{ + public string Type { get; set; } = null!; + public object Data { get; set; } + public string? ErrorMessage { get; set; } + + /// + /// Creates a WebSocketPacket from raw WebSocket message bytes + /// + /// Raw WebSocket message bytes + /// Deserialized WebSocketPacket + public static WebSocketPacket FromBytes(byte[] bytes) + { + var json = System.Text.Encoding.UTF8.GetString(bytes); + var jsonOpts = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, + }; + return JsonSerializer.Deserialize(json, jsonOpts) ?? + throw new JsonException("Failed to deserialize WebSocketPacket"); + } + + /// + /// Deserializes the Data property to the specified type T + /// + /// Target type to deserialize to + /// Deserialized data of type T + public T? GetData() + { + if (Data == null) + return default; + if (Data is T typedData) + return typedData; + + return JsonSerializer.Deserialize( + JsonSerializer.Serialize(Data) + ); + } + + /// + /// Serializes this WebSocketPacket to a byte array for sending over WebSocket + /// + /// Byte array representation of the packet + public byte[] ToBytes() + { + var jsonOpts = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower, + }; + var json = JsonSerializer.Serialize(this, jsonOpts); + return System.Text.Encoding.UTF8.GetBytes(json); + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Connection/WebSocketService.cs b/DysonNetwork.Sphere/Connection/WebSocketService.cs index 61e548d..8b536b4 100644 --- a/DysonNetwork.Sphere/Connection/WebSocketService.cs +++ b/DysonNetwork.Sphere/Connection/WebSocketService.cs @@ -3,7 +3,7 @@ using System.Net.WebSockets; namespace DysonNetwork.Sphere.Connection; -public class WebSocketService +public class WebSocketService(ChatService cs) { public static readonly ConcurrentDictionary< (long AccountId, string DeviceId), @@ -32,4 +32,41 @@ public class WebSocketService data.Cts.Cancel(); ActiveConnections.TryRemove(key, out _); } + + public void HandlePacket(Account.Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket) + { + switch (packet.Type) + { + case "message.read": + var request = packet.GetData(); + if (request is null) + { + socket.SendAsync( + new ArraySegment(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(new WebSocketPacket + { + Type = WebSocketPacketType.Error, + ErrorMessage = $"Unprocessable packet: {packet.Type}" + }.ToBytes()), + WebSocketMessageType.Binary, + true, + CancellationToken.None + ); + break; + } + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.Designer.cs b/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.Designer.cs deleted file mode 100644 index 7ca88d0..0000000 --- a/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.Designer.cs +++ /dev/null @@ -1,1744 +0,0 @@ -// -using System; -using System.Collections.Generic; -using System.Text.Json; -using DysonNetwork.Sphere; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - [DbContext(typeof(AppDatabase))] - [Migration("20250501080049_InitialMigration")] - partial class InitialMigration - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("activated_at"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("IsSuperuser") - .HasColumnType("boolean") - .HasColumnName("is_superuser"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("language"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Nick") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("nick"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_accounts"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_accounts_name"); - - b.ToTable("accounts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Secret") - .HasColumnType("text") - .HasColumnName("secret"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_auth_factors"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_account_auth_factors_account_id"); - - b.ToTable("account_auth_factors", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("Content") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("content"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_account_contacts"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_account_contacts_account_id"); - - b.ToTable("account_contacts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("AffectedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("affected_at"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property>("Meta") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Spell") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("spell"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_magic_spells"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_magic_spells_account_id"); - - b.HasIndex("Spell") - .IsUnique() - .HasDatabaseName("ix_magic_spells_spell"); - - b.ToTable("magic_spells", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("Content") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("content"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Subtitle") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("subtitle"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Topic") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("topic"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("ViewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("viewed_at"); - - b.HasKey("Id") - .HasName("pk_notifications"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notifications_account_id"); - - b.ToTable("notifications", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeviceId") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_id"); - - b.Property("DeviceToken") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("device_token"); - - b.Property("LastUsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_used_at"); - - b.Property("Provider") - .HasColumnType("integer") - .HasColumnName("provider"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_notification_push_subscriptions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_notification_push_subscriptions_account_id"); - - b.HasIndex("DeviceId") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_id"); - - b.HasIndex("DeviceToken") - .IsUnique() - .HasDatabaseName("ix_notification_push_subscriptions_device_token"); - - b.ToTable("notification_push_subscriptions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(128)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("FirstName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("first_name"); - - b.Property("LastName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("last_name"); - - b.Property("MiddleName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("middle_name"); - - b.Property("PictureId") - .HasColumnType("character varying(128)") - .HasColumnName("picture_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_account_profiles"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_account_profiles_background_id"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_account_profiles_picture_id"); - - b.ToTable("account_profiles", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("RelatedId") - .HasColumnType("bigint") - .HasColumnName("related_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Status") - .HasColumnType("integer") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("AccountId", "RelatedId") - .HasName("pk_account_relationships"); - - b.HasIndex("RelatedId") - .HasDatabaseName("ix_account_relationships_related_id"); - - b.ToTable("account_relationships", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ResourceIdentifier") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("resource_identifier"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_activities"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_activities_account_id"); - - b.ToTable("activities", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property>("Audiences") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("audiences"); - - b.Property>("BlacklistFactors") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("blacklist_factors"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeviceId") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("device_id"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("FailedAttempts") - .HasColumnType("integer") - .HasColumnName("failed_attempts"); - - b.Property("IpAddress") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("ip_address"); - - b.Property("Nonce") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("nonce"); - - b.Property("Platform") - .HasColumnType("integer") - .HasColumnName("platform"); - - b.Property>("Scopes") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("scopes"); - - b.Property("StepRemain") - .HasColumnType("integer") - .HasColumnName("step_remain"); - - b.Property("StepTotal") - .HasColumnType("integer") - .HasColumnName("step_total"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UserAgent") - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_auth_challenges"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_challenges_account_id"); - - b.ToTable("auth_challenges", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("ChallengeId") - .HasColumnType("uuid") - .HasColumnName("challenge_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("Label") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("label"); - - b.Property("LastGrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_granted_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_auth_sessions_account_id"); - - b.HasIndex("ChallengeId") - .HasDatabaseName("ix_auth_sessions_challenge_id"); - - b.ToTable("auth_sessions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("key"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_permission_groups"); - - b.ToTable("permission_groups", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.Property("GroupId") - .HasColumnType("uuid") - .HasColumnName("group_id"); - - b.Property("Actor") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("actor"); - - b.Property("AffectedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("affected_at"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("GroupId", "Actor") - .HasName("pk_permission_group_members"); - - b.ToTable("permission_group_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("Actor") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("actor"); - - b.Property("AffectedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("affected_at"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("area"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property("GroupId") - .HasColumnType("uuid") - .HasColumnName("group_id"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("key"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Value") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("value"); - - b.HasKey("Id") - .HasName("pk_permission_nodes"); - - b.HasIndex("GroupId") - .HasDatabaseName("ix_permission_nodes_group_id"); - - b.HasIndex("Key", "Area", "Actor") - .HasDatabaseName("ix_permission_nodes_key_area_actor"); - - b.ToTable("permission_nodes", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .HasColumnType("jsonb") - .HasColumnName("content"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Downvotes") - .HasColumnType("integer") - .HasColumnName("downvotes"); - - b.Property("EditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("edited_at"); - - b.Property("ForwardedPostId") - .HasColumnType("bigint") - .HasColumnName("forwarded_post_id"); - - b.Property("Language") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("language"); - - b.Property>("Meta") - .HasColumnType("jsonb") - .HasColumnName("meta"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublisherId") - .HasColumnType("bigint") - .HasColumnName("publisher_id"); - - b.Property("RepliedPostId") - .HasColumnType("bigint") - .HasColumnName("replied_post_id"); - - b.Property("SearchVector") - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("tsvector") - .HasColumnName("search_vector") - .HasAnnotation("Npgsql:TsVectorConfig", "simple") - .HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" }); - - b.Property("ThreadedPostId") - .HasColumnType("bigint") - .HasColumnName("threaded_post_id"); - - b.Property("Title") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("title"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("Upvotes") - .HasColumnType("integer") - .HasColumnName("upvotes"); - - b.Property("ViewsTotal") - .HasColumnType("integer") - .HasColumnName("views_total"); - - b.Property("ViewsUnique") - .HasColumnType("integer") - .HasColumnName("views_unique"); - - b.Property("Visibility") - .HasColumnType("integer") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_posts"); - - b.HasIndex("ForwardedPostId") - .HasDatabaseName("ix_posts_forwarded_post_id"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_posts_publisher_id"); - - b.HasIndex("RepliedPostId") - .HasDatabaseName("ix_posts_replied_post_id"); - - b.HasIndex("SearchVector") - .HasDatabaseName("ix_posts_search_vector"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN"); - - b.HasIndex("ThreadedPostId") - .IsUnique() - .HasDatabaseName("ix_posts_threaded_post_id"); - - b.ToTable("posts", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_categories"); - - b.ToTable("post_categories", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("PublisherId") - .HasColumnType("bigint") - .HasColumnName("publisher_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_collections"); - - b.HasIndex("PublisherId") - .HasDatabaseName("ix_post_collections_publisher_id"); - - b.ToTable("post_collections", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("Attitude") - .HasColumnType("integer") - .HasColumnName("attitude"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("PostId") - .HasColumnType("bigint") - .HasColumnName("post_id"); - - b.Property("Symbol") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("symbol"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_reactions"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_post_reactions_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_post_reactions_post_id"); - - b.ToTable("post_reactions", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("slug"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_post_tags"); - - b.ToTable("post_tags", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("BackgroundId") - .HasColumnType("character varying(128)") - .HasColumnName("background_id"); - - b.Property("Bio") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("bio"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("name"); - - b.Property("Nick") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("nick"); - - b.Property("PictureId") - .HasColumnType("character varying(128)") - .HasColumnName("picture_id"); - - b.Property("PublisherType") - .HasColumnType("integer") - .HasColumnName("publisher_type"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("Id") - .HasName("pk_publishers"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publishers_account_id"); - - b.HasIndex("BackgroundId") - .HasDatabaseName("ix_publishers_background_id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("ix_publishers_name"); - - b.HasIndex("PictureId") - .HasDatabaseName("ix_publishers_picture_id"); - - b.ToTable("publishers", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b => - { - b.Property("PublisherId") - .HasColumnType("bigint") - .HasColumnName("publisher_id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("JoinedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("PublisherId", "AccountId") - .HasName("pk_publisher_members"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_publisher_members_account_id"); - - b.ToTable("publisher_members", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.Property("Id") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("id"); - - b.Property("AccountId") - .HasColumnType("bigint") - .HasColumnName("account_id"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("Description") - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("description"); - - b.Property("ExpiredAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expired_at"); - - b.Property>("FileMeta") - .HasColumnType("jsonb") - .HasColumnName("file_meta"); - - b.Property("HasCompression") - .HasColumnType("boolean") - .HasColumnName("has_compression"); - - b.Property("Hash") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("hash"); - - b.Property("MimeType") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("mime_type"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("name"); - - b.Property("PostId") - .HasColumnType("bigint") - .HasColumnName("post_id"); - - b.Property("Size") - .HasColumnType("bigint") - .HasColumnName("size"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.Property("UploadedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("uploaded_at"); - - b.Property("UploadedTo") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("uploaded_to"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property>("UserMeta") - .HasColumnType("jsonb") - .HasColumnName("user_meta"); - - b.HasKey("Id") - .HasName("pk_files"); - - b.HasIndex("AccountId") - .HasDatabaseName("ix_files_account_id"); - - b.HasIndex("PostId") - .HasDatabaseName("ix_files_post_id"); - - b.ToTable("files", (string)null); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.Property("CategoriesId") - .HasColumnType("bigint") - .HasColumnName("categories_id"); - - b.Property("PostsId") - .HasColumnType("bigint") - .HasColumnName("posts_id"); - - b.HasKey("CategoriesId", "PostsId") - .HasName("pk_post_category_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_category_links_posts_id"); - - b.ToTable("post_category_links", (string)null); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.Property("CollectionsId") - .HasColumnType("bigint") - .HasColumnName("collections_id"); - - b.Property("PostsId") - .HasColumnType("bigint") - .HasColumnName("posts_id"); - - b.HasKey("CollectionsId", "PostsId") - .HasName("pk_post_collection_links"); - - b.HasIndex("PostsId") - .HasDatabaseName("ix_post_collection_links_posts_id"); - - b.ToTable("post_collection_links", (string)null); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.Property("PostsId") - .HasColumnType("bigint") - .HasColumnName("posts_id"); - - b.Property("TagsId") - .HasColumnType("bigint") - .HasColumnName("tags_id"); - - b.HasKey("PostsId", "TagsId") - .HasName("pk_post_tag_links"); - - b.HasIndex("TagsId") - .HasDatabaseName("ix_post_tag_links_tags_id"); - - b.ToTable("post_tag_links", (string)null); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("AuthFactors") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_auth_factors_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Contacts") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_contacts_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.MagicSpell", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_magic_spells_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notifications_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b => - { - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_account_profiles_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithOne("Profile") - .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_profiles_accounts_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_account_profiles_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("OutgoingRelationships") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Account.Account", "Related") - .WithMany("IncomingRelationships") - .HasForeignKey("RelatedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_account_relationships_accounts_related_id"); - - b.Navigation("Account"); - - b.Navigation("Related"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activities_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Challenges") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_accounts_account_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany("Sessions") - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge") - .WithMany() - .HasForeignKey("ChallengeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id"); - - b.Navigation("Account"); - - b.Navigation("Challenge"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Members") - .HasForeignKey("GroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_permission_group_members_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionNode", b => - { - b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group") - .WithMany("Nodes") - .HasForeignKey("GroupId") - .HasConstraintName("fk_permission_nodes_permission_groups_group_id"); - - b.Navigation("Group"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost") - .WithMany() - .HasForeignKey("ForwardedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_forwarded_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher") - .WithMany("Posts") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_posts_publishers_publisher_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost") - .WithMany() - .HasForeignKey("RepliedPostId") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_posts_posts_replied_post_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost") - .WithOne() - .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId") - .HasConstraintName("fk_posts_posts_threaded_post_id"); - - b.Navigation("ForwardedPost"); - - b.Navigation("Publisher"); - - b.Navigation("RepliedPost"); - - b.Navigation("ThreadedPost"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher") - .WithMany("Collections") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collections_publishers_publisher_id"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", "Post") - .WithMany("Reactions") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_reactions_posts_post_id"); - - b.Navigation("Account"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .HasConstraintName("fk_publishers_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background") - .WithMany() - .HasForeignKey("BackgroundId") - .HasConstraintName("fk_publishers_files_background_id"); - - b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture") - .WithMany() - .HasForeignKey("PictureId") - .HasConstraintName("fk_publishers_files_picture_id"); - - b.Navigation("Account"); - - b.Navigation("Background"); - - b.Navigation("Picture"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher") - .WithMany("Members") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_publisher_members_publishers_publisher_id"); - - b.Navigation("Account"); - - b.Navigation("Publisher"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b => - { - b.HasOne("DysonNetwork.Sphere.Account.Account", "Account") - .WithMany() - .HasForeignKey("AccountId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_files_accounts_account_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany("Attachments") - .HasForeignKey("PostId") - .HasConstraintName("fk_files_posts_post_id"); - - b.Navigation("Account"); - }); - - modelBuilder.Entity("PostPostCategory", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null) - .WithMany() - .HasForeignKey("CategoriesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_post_categories_categories_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_category_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostCollection", b => - { - b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null) - .WithMany() - .HasForeignKey("CollectionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_post_collections_collections_id"); - - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_collection_links_posts_posts_id"); - }); - - modelBuilder.Entity("PostPostTag", b => - { - b.HasOne("DysonNetwork.Sphere.Post.Post", null) - .WithMany() - .HasForeignKey("PostsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_posts_posts_id"); - - b.HasOne("DysonNetwork.Sphere.Post.PostTag", null) - .WithMany() - .HasForeignKey("TagsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_post_tag_links_post_tags_tags_id"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b => - { - b.Navigation("AuthFactors"); - - b.Navigation("Challenges"); - - b.Navigation("Contacts"); - - b.Navigation("IncomingRelationships"); - - b.Navigation("OutgoingRelationships"); - - b.Navigation("Profile") - .IsRequired(); - - b.Navigation("Sessions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b => - { - b.Navigation("Members"); - - b.Navigation("Nodes"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b => - { - b.Navigation("Attachments"); - - b.Navigation("Reactions"); - }); - - modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b => - { - b.Navigation("Collections"); - - b.Navigation("Members"); - - b.Navigation("Posts"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250502040651_RealmAndChat.cs b/DysonNetwork.Sphere/Migrations/20250502040651_RealmAndChat.cs deleted file mode 100644 index 0c30f0c..0000000 --- a/DysonNetwork.Sphere/Migrations/20250502040651_RealmAndChat.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace DysonNetwork.Sphere.Migrations -{ - /// - public partial class RealmAndChat : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "created_at", - table: "publisher_members", - type: "timestamp with time zone", - nullable: false, - defaultValue: NodaTime.Instant.FromUnixTimeTicks(0L)); - - migrationBuilder.AddColumn( - name: "deleted_at", - table: "publisher_members", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "updated_at", - table: "publisher_members", - type: "timestamp with time zone", - nullable: false, - defaultValue: NodaTime.Instant.FromUnixTimeTicks(0L)); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "created_at", - table: "publisher_members"); - - migrationBuilder.DropColumn( - name: "deleted_at", - table: "publisher_members"); - - migrationBuilder.DropColumn( - name: "updated_at", - table: "publisher_members"); - } - } -} diff --git a/DysonNetwork.Sphere/Migrations/20250502040651_RealmAndChat.Designer.cs b/DysonNetwork.Sphere/Migrations/20250502041309_InitialMigration.Designer.cs similarity index 99% rename from DysonNetwork.Sphere/Migrations/20250502040651_RealmAndChat.Designer.cs rename to DysonNetwork.Sphere/Migrations/20250502041309_InitialMigration.Designer.cs index f8ad1ea..869b4a6 100644 --- a/DysonNetwork.Sphere/Migrations/20250502040651_RealmAndChat.Designer.cs +++ b/DysonNetwork.Sphere/Migrations/20250502041309_InitialMigration.Designer.cs @@ -16,8 +16,8 @@ using NpgsqlTypes; namespace DysonNetwork.Sphere.Migrations { [DbContext(typeof(AppDatabase))] - [Migration("20250502040651_RealmAndChat")] - partial class RealmAndChat + [Migration("20250502041309_InitialMigration")] + partial class InitialMigration { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) diff --git a/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.cs b/DysonNetwork.Sphere/Migrations/20250502041309_InitialMigration.cs similarity index 83% rename from DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.cs rename to DysonNetwork.Sphere/Migrations/20250502041309_InitialMigration.cs index 90346c4..060623e 100644 --- a/DysonNetwork.Sphere/Migrations/20250501080049_InitialMigration.cs +++ b/DysonNetwork.Sphere/Migrations/20250502041309_InitialMigration.cs @@ -405,6 +405,52 @@ namespace DysonNetwork.Sphere.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "chat_members", + columns: table => new + { + chat_room_id = table.Column(type: "bigint", nullable: false), + account_id = table.Column(type: "bigint", nullable: false), + role = table.Column(type: "integer", nullable: false), + joined_at = table.Column(type: "timestamp with time zone", nullable: true), + is_bot = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_chat_members", x => new { x.chat_room_id, x.account_id }); + table.ForeignKey( + name: "fk_chat_members_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "chat_rooms", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + type = table.Column(type: "integer", nullable: false), + is_public = table.Column(type: "boolean", nullable: false), + picture_id = table.Column(type: "character varying(128)", nullable: true), + background_id = table.Column(type: "character varying(128)", nullable: true), + realm_id = table.Column(type: "bigint", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_chat_rooms", x => x.id); + }); + migrationBuilder.CreateTable( name: "files", columns: table => new @@ -476,6 +522,47 @@ namespace DysonNetwork.Sphere.Migrations principalColumn: "id"); }); + migrationBuilder.CreateTable( + name: "realms", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + slug = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + name = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: false), + description = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: false), + verified_as = table.Column(type: "character varying(4096)", maxLength: 4096, nullable: true), + verified_at = table.Column(type: "timestamp with time zone", nullable: true), + is_community = table.Column(type: "boolean", nullable: false), + is_public = table.Column(type: "boolean", nullable: false), + picture_id = table.Column(type: "character varying(128)", nullable: true), + background_id = table.Column(type: "character varying(128)", nullable: true), + account_id = table.Column(type: "bigint", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_realms", x => x.id); + table.ForeignKey( + name: "fk_realms_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_realms_files_background_id", + column: x => x.background_id, + principalTable: "files", + principalColumn: "id"); + table.ForeignKey( + name: "fk_realms_files_picture_id", + column: x => x.picture_id, + principalTable: "files", + principalColumn: "id"); + }); + migrationBuilder.CreateTable( name: "post_collections", columns: table => new @@ -588,6 +675,35 @@ namespace DysonNetwork.Sphere.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "realm_members", + columns: table => new + { + realm_id = table.Column(type: "bigint", nullable: false), + account_id = table.Column(type: "bigint", nullable: false), + role = table.Column(type: "integer", nullable: false), + joined_at = table.Column(type: "timestamp with time zone", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_realm_members", x => new { x.realm_id, x.account_id }); + table.ForeignKey( + name: "fk_realm_members_accounts_account_id", + column: x => x.account_id, + principalTable: "accounts", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_realm_members_realms_realm_id", + column: x => x.realm_id, + principalTable: "realms", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "post_category_links", columns: table => new @@ -742,6 +858,26 @@ namespace DysonNetwork.Sphere.Migrations table: "auth_sessions", column: "challenge_id"); + migrationBuilder.CreateIndex( + name: "ix_chat_members_account_id", + table: "chat_members", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_chat_rooms_background_id", + table: "chat_rooms", + column: "background_id"); + + migrationBuilder.CreateIndex( + name: "ix_chat_rooms_picture_id", + table: "chat_rooms", + column: "picture_id"); + + migrationBuilder.CreateIndex( + name: "ix_chat_rooms_realm_id", + table: "chat_rooms", + column: "realm_id"); + migrationBuilder.CreateIndex( name: "ix_files_account_id", table: "files", @@ -878,6 +1014,32 @@ namespace DysonNetwork.Sphere.Migrations table: "publishers", column: "picture_id"); + migrationBuilder.CreateIndex( + name: "ix_realm_members_account_id", + table: "realm_members", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_realms_account_id", + table: "realms", + column: "account_id"); + + migrationBuilder.CreateIndex( + name: "ix_realms_background_id", + table: "realms", + column: "background_id"); + + migrationBuilder.CreateIndex( + name: "ix_realms_picture_id", + table: "realms", + column: "picture_id"); + + migrationBuilder.CreateIndex( + name: "ix_realms_slug", + table: "realms", + column: "slug", + unique: true); + migrationBuilder.AddForeignKey( name: "fk_account_profiles_files_background_id", table: "account_profiles", @@ -892,6 +1054,35 @@ namespace DysonNetwork.Sphere.Migrations principalTable: "files", principalColumn: "id"); + migrationBuilder.AddForeignKey( + name: "fk_chat_members_chat_rooms_chat_room_id", + table: "chat_members", + column: "chat_room_id", + principalTable: "chat_rooms", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_chat_rooms_files_background_id", + table: "chat_rooms", + column: "background_id", + principalTable: "files", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_chat_rooms_files_picture_id", + table: "chat_rooms", + column: "picture_id", + principalTable: "files", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_chat_rooms_realms_realm_id", + table: "chat_rooms", + column: "realm_id", + principalTable: "realms", + principalColumn: "id"); + migrationBuilder.AddForeignKey( name: "fk_files_posts_post_id", table: "files", @@ -937,6 +1128,9 @@ namespace DysonNetwork.Sphere.Migrations migrationBuilder.DropTable( name: "auth_sessions"); + migrationBuilder.DropTable( + name: "chat_members"); + migrationBuilder.DropTable( name: "magic_spells"); @@ -967,9 +1161,15 @@ namespace DysonNetwork.Sphere.Migrations migrationBuilder.DropTable( name: "publisher_members"); + migrationBuilder.DropTable( + name: "realm_members"); + migrationBuilder.DropTable( name: "auth_challenges"); + migrationBuilder.DropTable( + name: "chat_rooms"); + migrationBuilder.DropTable( name: "permission_groups"); @@ -982,6 +1182,9 @@ namespace DysonNetwork.Sphere.Migrations migrationBuilder.DropTable( name: "post_tags"); + migrationBuilder.DropTable( + name: "realms"); + migrationBuilder.DropTable( name: "accounts"); diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index 6160f46..4d44881 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -59,7 +59,7 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act) break; } - using var newDocument = JsonDocument.Parse(JsonSerializer.Serialize(truncatedArrayElements)); + var newDocument = JsonDocument.Parse(JsonSerializer.Serialize(truncatedArrayElements)); item.Content = newDocument; } diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index 2b2d7bb..8567b27 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -9,9 +9,11 @@ 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.Permission; using DysonNetwork.Sphere.Post; +using DysonNetwork.Sphere.Realm; using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.HttpOverrides; @@ -128,6 +130,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Timed task diff --git a/DysonNetwork.Sphere/Realm/RealmChatController.cs b/DysonNetwork.Sphere/Realm/RealmChatController.cs index 1a3f5ba..ab0dfcf 100644 --- a/DysonNetwork.Sphere/Realm/RealmChatController.cs +++ b/DysonNetwork.Sphere/Realm/RealmChatController.cs @@ -9,7 +9,7 @@ namespace DysonNetwork.Sphere.Realm; [Route("/realm/{slug}")] public class RealmChatController(AppDatabase db) : ControllerBase { - [HttpGet("/chat")] + [HttpGet("chat")] [Authorize] public async Task>> ListRealmChat(string slug) { diff --git a/DysonNetwork.Sphere/Realm/RealmController.cs b/DysonNetwork.Sphere/Realm/RealmController.cs index 9c20d14..0c6f150 100644 --- a/DysonNetwork.Sphere/Realm/RealmController.cs +++ b/DysonNetwork.Sphere/Realm/RealmController.cs @@ -167,8 +167,8 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs) : public async Task> CreateRealm(RealmRequest request) { if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); - if (request.Name is null) return BadRequest("You cannot create a realm without a name."); - if (request.Slug is null) return BadRequest("You cannot create a realm without a slug."); + if (string.IsNullOrWhiteSpace(request.Name)) return BadRequest("You cannot create a realm without a name."); + if (string.IsNullOrWhiteSpace(request.Slug)) return BadRequest("You cannot create a realm without a slug."); var slugExists = await db.Realms.AnyAsync(r => r.Slug == request.Slug); if (slugExists) return BadRequest("Realm with this slug already exists."); @@ -186,7 +186,8 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs) : new() { Role = RealmMemberRole.Owner, - AccountId = currentUser.Id + AccountId = currentUser.Id, + JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow) } } }; @@ -212,7 +213,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs) : return Ok(realm); } - [HttpPut("{slug}")] + [HttpPatch("{slug}")] [Authorize] public async Task> Update(string slug, [FromBody] RealmRequest request) { diff --git a/DysonNetwork.Sphere/Storage/FileController.cs b/DysonNetwork.Sphere/Storage/FileController.cs index 3f42e0e..0232429 100644 --- a/DysonNetwork.Sphere/Storage/FileController.cs +++ b/DysonNetwork.Sphere/Storage/FileController.cs @@ -15,7 +15,7 @@ public class FileController( ) : ControllerBase { [HttpGet("{id}")] - public async Task OpenFile(string id) + public async Task OpenFile(string id, [FromQuery] bool original = false) { var file = await db.Files.FindAsync(id); if (file is null) return NotFound(); @@ -29,12 +29,18 @@ public class FileController( } var dest = fs.GetRemoteStorageConfig(file.UploadedTo); + var fileName = file.Id; + + if (!original && file.HasCompression) + { + fileName += ".compressed"; + } if (dest.ImageProxy is not null && (file.MimeType?.StartsWith("image/") ?? false)) { var proxyUrl = dest.ImageProxy; var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); - var fullUri = new Uri(baseUri, file.Id); + var fullUri = new Uri(baseUri, fileName); return Redirect(fullUri.ToString()); } @@ -42,7 +48,7 @@ public class FileController( { var proxyUrl = dest.AccessProxy; var baseUri = new Uri(proxyUrl.EndsWith('/') ? proxyUrl : $"{proxyUrl}/"); - var fullUri = new Uri(baseUri, file.Id); + var fullUri = new Uri(baseUri, fileName); return Redirect(fullUri.ToString()); } @@ -67,7 +73,7 @@ public class FileController( // Fallback redirect to the S3 endpoint (public read) var protocol = dest.EnableSsl ? "https" : "http"; // Use the path bucket lookup mode - return Redirect($"{protocol}://{dest.Endpoint}/{dest.Bucket}/{file.Id}"); + return Redirect($"{protocol}://{dest.Endpoint}/{dest.Bucket}/{fileName}"); } [HttpGet("{id}/info")]