diff --git a/.gitignore b/.gitignore index 793ea0b3..6c05074b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ riderModule.iml src/Argon.Api/storage/ secrets.json + +src/Argon.Api/Generated/ diff --git a/src/Argon.Api/Controllers/AuthorizationController.cs b/src/Argon.Api/Controllers/AuthorizationController.cs index ed34a0ff..d8675117 100644 --- a/src/Argon.Api/Controllers/AuthorizationController.cs +++ b/src/Argon.Api/Controllers/AuthorizationController.cs @@ -2,8 +2,8 @@ namespace Argon.Api.Controllers; using Grains.Interfaces; using Contracts; -using Extensions; using Microsoft.AspNetCore.Mvc; +using Argon.Features; [ApiController] public class AuthorizationController(IGrainFactory grainFactory) : ControllerBase diff --git a/src/Argon.Api/Controllers/MetadataController.cs b/src/Argon.Api/Controllers/MetadataController.cs index 3d399abf..c400a3dd 100644 --- a/src/Argon.Api/Controllers/MetadataController.cs +++ b/src/Argon.Api/Controllers/MetadataController.cs @@ -1,5 +1,6 @@ namespace Argon.Api.Controllers; +using Argon.Features; using Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/Argon.Api/Entities/ApplicationDbContext.cs b/src/Argon.Api/Entities/ApplicationDbContext.cs index b86ba8bb..b043ae44 100644 --- a/src/Argon.Api/Entities/ApplicationDbContext.cs +++ b/src/Argon.Api/Entities/ApplicationDbContext.cs @@ -1,12 +1,154 @@ namespace Argon.Api.Entities; +using System.Drawing; +using System.Linq.Expressions; +using Contracts.Models; +using Contracts.Models.ArchetypeModel; +using Features.EF; using Microsoft.EntityFrameworkCore; public class ApplicationDbContext(DbContextOptions options) : DbContext(options) { - public DbSet Users { get; set; } - public DbSet UserAgreements { get; set; } - public DbSet Servers { get; set; } - public DbSet Channels { get; set; } - public DbSet UsersToServerRelations { get; set; } + public DbSet Users { get; set; } + public DbSet UserAgreements { get; set; } + public DbSet Servers { get; set; } + public DbSet Channels { get; set; } + public DbSet UsersToServerRelations { get; set; } + + public DbSet ServerMemberArchetypes { get; set; } + public DbSet Archetypes { get; set; } + public DbSet ChannelEntitlementOverwrites { get; set; } + + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(x => new { x.ServerMemberId, x.ArchetypeId }); + + modelBuilder.Entity() + .HasOne(x => x.ServerMember) + .WithMany(x => x.ServerMemberArchetypes) + .HasForeignKey(x => x.ServerMemberId); + + modelBuilder.Entity() + .HasOne(x => x.Archetype) + .WithMany(x => x.ServerMemberRoles) + .HasForeignKey(x => x.ArchetypeId); + + modelBuilder.Entity() + .HasOne(x => x.Server) + .WithMany(x => x.Archetypes) + .HasForeignKey(x => x.ServerId); + + modelBuilder.Entity() + .Property(x => x.Colour) + .HasConversion(); + + modelBuilder.Entity() + .HasOne(x => x.Server) + .WithMany(x => x.Users) + .HasForeignKey(x => x.ServerId); + + modelBuilder.Entity() + .HasOne(x => x.User) + .WithMany(x => x.ServerMembers) + .HasForeignKey(x => x.UserId); + + modelBuilder.Entity() + .HasOne(c => c.Server) + .WithMany(s => s.Channels) + .HasForeignKey(c => c.ServerId); + + modelBuilder.Entity() + .HasIndex(x => new { x.Id, x.ServerId }); + + modelBuilder.Entity() + .HasOne(cpo => cpo.Channel) + .WithMany(c => c.EntitlementOverwrites) + .HasForeignKey(cpo => cpo.ChannelId); + + modelBuilder.Entity() + .HasOne(cpo => cpo.Archetype) + .WithMany() + .HasForeignKey(cpo => cpo.ArchetypeId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(cpo => cpo.ServerMember) + .WithMany() + .HasForeignKey(cpo => cpo.ServerMemberId) + .OnDelete(DeleteBehavior.Restrict); + + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + if (!typeof(ArgonEntity).IsAssignableFrom(entityType.ClrType)) + continue; + modelBuilder.Entity(entityType.ClrType) + .HasQueryFilter(GetSoftDeleteFilter(entityType.ClrType)); + } + + modelBuilder.Entity().HasData(new User + { + Username = "system", + DisplayName = "System", + Email = "system@argon.gl", + Id = User.SystemUser, + PasswordDigest = null + }); + + modelBuilder.Entity().HasData(new Server + { + Name = "system_server", + CreatorId = User.SystemUser, + Id = Server.DefaultSystemServer + }); + + modelBuilder.Entity().HasData([ + new Archetype + { + Id = Archetype.DefaultArchetype_Everyone, + Colour = Color.Gray, + CreatorId = User.SystemUser, + Entitlement = ArgonEntitlement.BaseMember, + ServerId = Server.DefaultSystemServer, + Name = "everyone", + IsLocked = false, + IsMentionable = true, + IsHidden = false, + Description = "Default role for everyone in this server", + CreatedAt = DateTime.UtcNow + } + ]); + + modelBuilder.Entity().HasData([ + new Archetype + { + Id = Archetype.DefaultArchetype_Owner, + Colour = Color.Gray, + CreatorId = User.SystemUser, + Entitlement = ArgonEntitlement.Administrator, + ServerId = Server.DefaultSystemServer, + Name = "owner", + IsLocked = true, + IsMentionable = false, + IsHidden = true, + Description = "Default role for owner in this server", + CreatedAt = DateTime.UtcNow + } + ]); + } + + private static LambdaExpression GetSoftDeleteFilter(Type type) + { + var parameter = Expression.Parameter(type, "e"); + var isDeletedProperty = Expression.Property(parameter, nameof(ArgonEntity.IsDeleted)); + var notDeleted = Expression.Not(isDeletedProperty); + return Expression.Lambda(notDeleted, parameter); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.AddInterceptors(new TimeStampAndSoftDeleteInterceptor()); + base.OnConfiguring(optionsBuilder); + } } \ No newline at end of file diff --git a/src/Argon.Api/Entities/Channel.cs b/src/Argon.Api/Entities/Channel.cs deleted file mode 100644 index 4b0369e8..00000000 --- a/src/Argon.Api/Entities/Channel.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Argon.Api.Entities; - -using System.ComponentModel.DataAnnotations; -using Contracts; - -public sealed record Channel -{ - public Guid Id { get; init; } = Guid.NewGuid(); - public DateTime CreatedAt { get; init; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - public Guid UserId { get; set; } = Guid.Empty; - public ChannelType ChannelType { get; set; } = ChannelType.Text; - public ServerRole AccessLevel { get; set; } = ServerRole.User; - public Guid ServerId { get; set; } = Guid.Empty; - [MaxLength(255)] - public string Name { get; set; } = string.Empty; - [MaxLength(255)] - public string Description { get; set; } = string.Empty; -} - -[DataContract, MemoryPackable(GenerateType.VersionTolerant), MessagePackObject, Serializable, GenerateSerializer, Alias(nameof(ChannelDto))] -public sealed partial record ChannelDto( - Guid Id, - DateTime CreatedAt, - DateTime UpdatedAt, - string Name, - string Description, - Guid UserId, - ChannelType ChannelType, - ServerRole AccessLevel, - Guid ServerId) -{ - [property: DataMember(Order = 9), MemoryPackOrder(9), Id(9)] - public Dictionary ConnectedUsers { get; set; } = []; - [DataMember(Order = 0), MemoryPackOrder(0), Id(0)] - public Guid Id { get; set; } = Id; - [DataMember(Order = 1), MemoryPackOrder(1), Id(1)] - public DateTime CreatedAt { get; set; } = CreatedAt; - [DataMember(Order = 2), MemoryPackOrder(2), Id(2)] - public DateTime UpdatedAt { get; set; } = UpdatedAt; - [DataMember(Order = 3), MemoryPackOrder(3), Id(3)] - public string Name { get; set; } = Name; - [DataMember(Order = 4), MemoryPackOrder(4), Id(4)] - public string Description { get; set; } = Description; - [DataMember(Order = 5), MemoryPackOrder(5), Id(5)] - public Guid UserId { get; set; } = UserId; - [DataMember(Order = 6), MemoryPackOrder(6), Id(6)] - public ChannelType ChannelType { get; set; } = ChannelType; - [DataMember(Order = 7), MemoryPackOrder(7), Id(7)] - public ServerRole AccessLevel { get; set; } = AccessLevel; - [DataMember(Order = 8), MemoryPackOrder(8), Id(8)] - public Guid ServerId { get; set; } = ServerId; -} \ No newline at end of file diff --git a/src/Argon.Api/Entities/Server.cs b/src/Argon.Api/Entities/Server.cs deleted file mode 100644 index 4bb58be9..00000000 --- a/src/Argon.Api/Entities/Server.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace Argon.Api.Entities; - -using System.ComponentModel.DataAnnotations; - -public sealed record Server -{ - public Guid Id { get; init; } - public DateTime CreatedAt { get; init; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - [MaxLength(255)] - public string Name { get; set; } = string.Empty; - [MaxLength(255)] - public string? Description { get; set; } = string.Empty; - [MaxLength(255)] - public string? AvatarUrl { get; set; } = string.Empty; - public List Channels { get; set; } = new(); - public List UsersToServerRelations { get; set; } = new(); -} - -[DataContract, MemoryPackable(GenerateType.CircularReference), Serializable, Alias(nameof(ServerDto))] -public partial record ServerDto -{ - [MemoryPackConstructor] - public ServerDto() {} - - public ServerDto(Guid Id, - DateTime CreatedAt, - DateTime UpdatedAt, - string Name, - string? Description, - string? AvatarUrl, - List Channels, - List Users) - { - this.Id = Id; - this.CreatedAt = CreatedAt; - this.UpdatedAt = UpdatedAt; - this.Name = Name; - this.Description = Description; - this.AvatarUrl = AvatarUrl; - this.Channels = Channels; - this.Users = Users; - } - - [DataMember(Order = 0), MemoryPackOrder(0), Id(0)] - public Guid Id { get; set; } - [DataMember(Order = 1), MemoryPackOrder(1), Id(1)] - public DateTime CreatedAt { get; set; } - [DataMember(Order = 2), MemoryPackOrder(2), Id(2)] - public DateTime UpdatedAt { get; set; } - [DataMember(Order = 3), MemoryPackOrder(3), Id(3)] - public string Name { get; set; } - [DataMember(Order = 4), MemoryPackOrder(4), Id(4)] - public string? Description { get; set; } - [DataMember(Order = 5), MemoryPackOrder(5), Id(5)] - public string? AvatarUrl { get; set; } - [DataMember(Order = 6), MemoryPackOrder(6), Id(6)] - public List Channels { get; set; } - [DataMember(Order = 7), MemoryPackOrder(7), Id(7)] - public List Users { get; set; } -} \ No newline at end of file diff --git a/src/Argon.Api/Entities/User.cs b/src/Argon.Api/Entities/User.cs deleted file mode 100644 index e637ead5..00000000 --- a/src/Argon.Api/Entities/User.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace Argon.Api.Entities; - -using System.ComponentModel.DataAnnotations; - -public sealed record User -{ - public Guid Id { get; init; } = Guid.NewGuid(); - public DateTime CreatedAt { get; init; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - [Required, MaxLength(255)] - public string Email { get; set; } = string.Empty; - [MaxLength(255)] - public string? Username { get; set; } = string.Empty; - [MaxLength(30)] - public string? DisplayName { get; set; } = string.Empty; - [MaxLength(30)] - public string? PhoneNumber { get; set; } = string.Empty; - [MaxLength(511)] - public string? PasswordDigest { get; set; } = string.Empty; - [MaxLength(1023)] - public string? AvatarFileId { get; set; } = string.Empty; - [MaxLength(128)] - public string? OtpHash { get; set; } = string.Empty; - public DateTime? DeletedAt { get; set; } - public List UsersToServerRelations { get; set; } = new(); -} - - -public record UserAgreements -{ - [Key] - public Guid Id { get; set; } = Guid.NewGuid(); - public bool AllowedSendOptionalEmails { get; set; } - public bool AgreeTOS { get; set; } - public DateTime CreatedAt { get; init; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - - public virtual User User { get; set; } - public Guid UserId { get; set; } -} - -[DataContract, MemoryPackable(GenerateType.CircularReference), Alias(nameof(UserDto))] -public partial record UserDto -{ - [MemoryPackConstructor] - public UserDto() { } - - public UserDto(Guid Id, - DateTime CreatedAt, - DateTime UpdatedAt, - string Email, - string? Username, - string? PhoneNumber, - string? AvatarUrl, - DateTime? DeletedAt, - List Servers) - { - this.Id = Id; - this.CreatedAt = CreatedAt; - this.UpdatedAt = UpdatedAt; - this.Email = Email; - this.Username = Username; - this.PhoneNumber = PhoneNumber; - this.AvatarUrl = AvatarUrl; - this.DeletedAt = DeletedAt; - this.Servers = Servers; - } - - [DataMember(Order = 0), MemoryPackOrder(0), Id(0)] - public Guid Id { get; set; } - [DataMember(Order = 1), MemoryPackOrder(1), Id(1)] - public DateTime CreatedAt { get; set; } - [DataMember(Order = 2), MemoryPackOrder(2), Id(2)] - public DateTime UpdatedAt { get; set; } - [DataMember(Order = 3), MemoryPackOrder(3), Id(3)] - public string Email { get; set; } - [DataMember(Order = 4), MemoryPackOrder(4), Id(4)] - public string? Username { get; set; } - [DataMember(Order = 5), MemoryPackOrder(5), Id(5)] - public string? PhoneNumber { get; set; } - [DataMember(Order = 6), MemoryPackOrder(6), Id(6)] - public string? AvatarUrl { get; set; } - [DataMember(Order = 7), MemoryPackOrder(7), Id(7)] - public DateTime? DeletedAt { get; set; } - [DataMember(Order = 8), MemoryPackIgnore, Id(8)] - public List Servers { get; set; } -} \ No newline at end of file diff --git a/src/Argon.Api/Entities/UserAgreements.cs b/src/Argon.Api/Entities/UserAgreements.cs new file mode 100644 index 00000000..0ac7e4a9 --- /dev/null +++ b/src/Argon.Api/Entities/UserAgreements.cs @@ -0,0 +1,18 @@ +namespace Argon.Api.Entities; + +using System.ComponentModel.DataAnnotations; +using Contracts.Models; + +[MessagePackObject(true)] +public record UserAgreements +{ + [Key] + public Guid Id { get; set; } = Guid.NewGuid(); + public bool AllowedSendOptionalEmails { get; set; } + public bool AgreeTOS { get; set; } + public DateTime CreatedAt { get; init; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public virtual User User { get; set; } + public Guid UserId { get; set; } +} \ No newline at end of file diff --git a/src/Argon.Api/Entities/UsersToServerRelation.cs b/src/Argon.Api/Entities/UsersToServerRelation.cs deleted file mode 100644 index 204f6564..00000000 --- a/src/Argon.Api/Entities/UsersToServerRelation.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Argon.Api.Entities; - -public enum ServerRole : ushort // TODO: sort out roles and how we actually want to handle them -{ - User, - Admin, - Owner -} - -public record UsersToServerRelation -{ - public Guid Id { get; init; } = Guid.NewGuid(); - public DateTime CreatedAt { get; init; } = DateTime.UtcNow; - public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; - public Guid ServerId { get; set; } = Guid.Empty; - public Server Server { get; set; } - public DateTime Joined { get; set; } = DateTime.UtcNow; - public ServerRole Role { get; set; } = ServerRole.User; - public Guid UserId { get; set; } = Guid.Empty; - public virtual User User { get; set; } -} - -[DataContract, MemoryPackable(GenerateType.VersionTolerant), MessagePackObject, Serializable, GenerateSerializer, - Alias(nameof(UsersToServerRelationDto))] -public sealed partial record UsersToServerRelationDto( - [property: DataMember(Order = 0), MemoryPackOrder(0), Id(0)] - DateTime Joined, - [property: DataMember(Order = 1), MemoryPackOrder(1), Id(1)] - ServerRole Role, - [property: DataMember(Order = 2), MemoryPackOrder(2), Id(2)] - Guid ServerId, - [property: DataMember(Order = 3), MemoryPackOrder(3), Id(3)] - UserDto User); \ No newline at end of file diff --git a/src/Argon.Contracts/Argon.Contracts.csproj b/src/Argon.Contracts/Argon.Contracts.csproj index 764d74a5..6f5ef9fe 100644 --- a/src/Argon.Contracts/Argon.Contracts.csproj +++ b/src/Argon.Contracts/Argon.Contracts.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -7,22 +7,24 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/src/Argon.Contracts/AuthorizationError.cs b/src/Argon.Contracts/AuthorizationError.cs index a8fd341e..47c32323 100644 --- a/src/Argon.Contracts/AuthorizationError.cs +++ b/src/Argon.Contracts/AuthorizationError.cs @@ -1,11 +1,8 @@ namespace Argon; -using MemoryPack; -using MessagePack; -using Orleans; -using System.Runtime.Serialization; +using Reinforced.Typings.Attributes; -[Alias("Argon.AuthorizationError"), GenerateSerializer] +[TsEnum] public enum AuthorizationError { BAD_CREDENTIALS, @@ -13,7 +10,7 @@ public enum AuthorizationError BAD_OTP } -[Alias("Argon.RegistrationError"), GenerateSerializer] +[TsEnum] public enum RegistrationError { USERNAME_ALREADY_TAKEN, diff --git a/src/Argon.Contracts/IUserAuthorization.cs b/src/Argon.Contracts/IUserAuthorization.cs index e9344b0a..97a988d9 100644 --- a/src/Argon.Contracts/IUserAuthorization.cs +++ b/src/Argon.Contracts/IUserAuthorization.cs @@ -1,57 +1,37 @@ namespace Argon.Contracts; -using MemoryPack; -using Orleans; +using MessagePack; +using Reinforced.Typings.Attributes; -[MemoryPackable, Serializable, GenerateSerializer, Alias(nameof(UserCredentialsInput))] +[MessagePackObject(true), TsInterface] public sealed partial record UserCredentialsInput( - [field: Id(0)] string Email, - [field: Id(1)] string? Username, - [field: Id(2)] string? PhoneNumber, - [field: Id(3)] string? Password, - [field: Id(4)] string? OtpCode); -[MemoryPackable, Serializable, GenerateSerializer, Alias(nameof(UserEditInput))] +[MessagePackObject(true), TsInterface] public sealed partial record UserEditInput( - [field: Id(0)] string? Username, - [field: Id(1)] string? DisplayName, - [field: Id(2)] string? AvatarId); -[MemoryPackable, Serializable, GenerateSerializer, Alias(nameof(NewUserCredentialsInput))] -public sealed partial record NewUserCredentialsInput( - [field: Id(0)] +[MessagePackObject(true), TsInterface] +public record NewUserCredentialsInput( string Email, - [field: Id(1)] string Username, - [field: Id(2)] string? PhoneNumber, - [field: Id(3)] string Password, - [field: Id(4)] string DisplayName, - [field: Id(5)] DateTime BirthDate, - [field: Id(6)] bool AgreeTos, - [field: Id(7)] bool AgreeOptionalEmails); -[MemoryPackable, Serializable, GenerateSerializer, Alias(nameof(UserConnectionInfo))] +[MessagePackObject(true), TsInterface] public sealed partial record UserConnectionInfo( - [field: Id(0)] string Region, - [field: Id(1)] string IpAddress, - [field: Id(2)] string ClientName, - [field: Id(3)] string HostName); \ No newline at end of file diff --git a/src/Argon.Contracts/IUserInteraction.cs b/src/Argon.Contracts/IUserInteraction.cs index 03ff957e..c3dafd04 100644 --- a/src/Argon.Contracts/IUserInteraction.cs +++ b/src/Argon.Contracts/IUserInteraction.cs @@ -1,31 +1,48 @@ namespace Argon.Contracts; -using System.Runtime.Serialization; using ActualLab.Collections; using ActualLab.Rpc; -using MemoryPack; using MessagePack; using Orleans; +using Reinforced.Typings.Attributes; +using System.Reactive; +using Models; +[TsInterface] public interface IUserInteraction : IRpcService { - Task GetMe(); - Task CreateServer(CreateServerRequest request); - Task> GetServers(); + Task GetMe(); + Task CreateServer(CreateServerRequest request); + Task> GetServers(); } +[TsInterface] public interface IServerInteraction : IRpcService { - ValueTask CreateChannel(Guid serverId, string name, ChannelType kind); - ValueTask DeleteChannel(Guid serverId, Guid channelId); - ValueTask JoinToVoiceChannel(Guid serverId, Guid channelId); + Task CreateChannel(CreateChannelRequest request); + Task DeleteChannel(DeleteChannelRequest request); + Task JoinToVoiceChannel(JoinToVoiceChannelRequest request); } +[TsInterface, MessagePackObject(true)] +public record CreateChannelRequest(Guid serverId, string name, ChannelType kind, string desc); + +[TsInterface, MessagePackObject(true)] +public record CreateChannelResponse(Guid serverId, Guid channelId); + +[TsInterface, MessagePackObject(true)] +public record DeleteChannelRequest(Guid serverId, Guid channelId); + +[TsInterface, MessagePackObject(true)] +public record JoinToVoiceChannelRequest(Guid serverId, Guid channelId); + +[TsInterface] public interface IEventBus : IRpcService { ValueTask> SubscribeToServerEvents(Guid ServerId); } +[TsEnum] public enum ChannelType : ushort { Text, @@ -33,47 +50,50 @@ public enum ChannelType : ushort Announcement } +[TsEnum] public enum ServerEventKind { } +[TsInterface] public interface IArgonEvent { public static string ProviderId => "argon.cluster.events"; public static string Namespace => $"@"; } -[MemoryPackable, GenerateSerializer] -public partial record ArgonEvent : IArgonEvent where T : ArgonEvent, IArgonEvent; +[TsInterface, MessagePackObject(true)] +public record ArgonEvent : IArgonEvent where T : ArgonEvent, IArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record JoinToServerUser([property: Id(0)] Guid userId) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record JoinToServerUser(Guid userId) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record LeaveFromServerUser([property: Id(0)] Guid userId) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record LeaveFromServerUser(Guid userId) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record JoinedToChannelUser([property: Id(0)] Guid userId, [property: Id(1)] Guid channelId) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record JoinedToChannelUser(Guid userId, Guid channelId) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record LeavedFromChannelUser([property: Id(0)] Guid userId, [property: Id(1)] Guid channelId) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record LeavedFromChannelUser(Guid userId, Guid channelId) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record ChannelCreated([property: Id(0)] Guid channelId) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record ChannelCreated(Channel channel) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record ChannelModified([property: Id(0)] Guid channelId, [property: Id(1)] PropertyBag bag) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record ChannelModified(Guid channelId, PropertyBag bag) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record ChannelRemoved([property: Id(0)] Guid channelId) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record ChannelRemoved(Guid channelId) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record UserChangedStatus([property: Id(0)] Guid userId, [property: Id(1)] UserStatus status, [property: Id(3)] PropertyBag bag) +[TsInterface, MessagePackObject(true)] +public record UserChangedStatus(Guid userId, UserStatus status, PropertyBag bag) : ArgonEvent; -[MemoryPackable, GenerateSerializer] -public partial record ServerModified([property: Id(0)] PropertyBag bag) : ArgonEvent; +[TsInterface, MessagePackObject(true)] +public record ServerModified(PropertyBag bag) : ArgonEvent; +[TsEnum] public enum UserStatus { Offline, @@ -85,82 +105,23 @@ public enum UserStatus DoNotDisturb } -[MemoryPackable] -public sealed partial record ServerDetailsRequest(Guid ServerId); - -[MemoryPackable] -public sealed partial record ServerUser( - UserResponse user, - string Role); - -[MemoryPackable] -public sealed partial record UserResponse( - Guid Id, - string Username, - string AvatarUrl, - string DisplayName, - DateTime CreatedAt, - DateTime UpdatedAt) -{ - public Guid Id { get; set; } = Id; - public string Username { get; set; } = Username; - public string AvatarUrl { get; set; } = AvatarUrl; - public string DisplayName { get; set; } = DisplayName; - public DateTime CreatedAt { get; set; } = CreatedAt; - public DateTime UpdatedAt { get; set; } = UpdatedAt; -} +[TsInterface, MessagePackObject(true)] +public sealed record ServerDetailsRequest(Guid ServerId); -[MemoryPackable] -public sealed partial record CreateServerRequest( +[TsInterface, MessagePackObject(true)] +public record CreateServerRequest( string Name, string Description, - string AvatarUrl); + string AvatarFileId); -[MemoryPackable] -public sealed partial record ServerDefinition( - Guid Id, - string Name, - string Description, - string AvatarUrl, - List Channels, - DateTime CreatedAt, - DateTime UpdatedAt) -{ - public Guid Id { get; set; } = Id; - public string Name { get; set; } = Name; - public string Description { get; set; } = Description; - public string AvatarUrl { get; set; } = AvatarUrl; - public List Channels { get; set; } = Channels; - public DateTime CreatedAt { get; set; } = CreatedAt; - public DateTime UpdatedAt { get; set; } = UpdatedAt; -} - -[MemoryPackable] -public sealed partial record ChannelDefinition( - Guid Id, - string Name, - string Description, - string CreatedBy, - string ChannelType, - string AccessLevel, - DateTime CreatedAt, - DateTime UpdatedAt) -{ - public Guid Id { get; set; } = Id; - public string Name { get; set; } = Name; - public string Description { get; set; } = Description; - public string CreatedBy { get; set; } = CreatedBy; - public string ChannelType { get; set; } = ChannelType; - public string AccessLevel { get; set; } = AccessLevel; - public DateTime CreatedAt { get; set; } = CreatedAt; - public DateTime UpdatedAt { get; set; } = UpdatedAt; -} +[TsInterface, MessagePackObject(true)] +public record ChannelRealtimeMember(Guid UserId); -[MemoryPackable] -public sealed partial record ChannelJoinRequest( +[TsInterface, MessagePackObject(true)] +public record ChannelJoinRequest( Guid ServerId, Guid ChannelId); -[MemoryPackable] -public sealed partial record ChannelJoinResponse( +[TsInterface, MessagePackObject(true)] +public sealed record ChannelJoinResponse( string Token); \ No newline at end of file diff --git a/src/Argon.Contracts/JwtToken.cs b/src/Argon.Contracts/JwtToken.cs index 583733b1..6265df23 100644 --- a/src/Argon.Contracts/JwtToken.cs +++ b/src/Argon.Contracts/JwtToken.cs @@ -1,7 +1,9 @@ namespace Argon; +using Argon.Contracts; using MemoryPack; using Orleans; +using Reinforced.Typings.Attributes; -[Serializable, GenerateSerializer, MemoryPackable, Alias(nameof(JwtToken))] +[Serializable, GenerateSerializer, MemoryPackable, Alias(nameof(JwtToken)), TsInterface] public partial record struct JwtToken([field: Id(0)] string token); \ No newline at end of file diff --git a/src/Argon.Contracts/Models/ArgonEntity.cs b/src/Argon.Contracts/Models/ArgonEntity.cs new file mode 100644 index 00000000..e8f09d1d --- /dev/null +++ b/src/Argon.Contracts/Models/ArgonEntity.cs @@ -0,0 +1,28 @@ +namespace Argon.Contracts.Models; + +using MessagePack; +using Microsoft.EntityFrameworkCore; + +[MessagePackObject(true)] +public abstract record ArgonEntity +{ + [System.ComponentModel.DataAnnotations.Key] + public Guid Id { get; set; } + + [IgnoreMember] + public DateTime CreatedAt { get; set; } + [IgnoreMember] + public DateTime UpdatedAt { get; set; } + [IgnoreMember] + public DateTime? DeletedAt { get; set; } + + [IgnoreMember] + public bool IsDeleted { get; set; } +} + + +[MessagePackObject(true), Index("CreatorId")] +public abstract record ArgonEntityWithOwnership : ArgonEntity +{ + public Guid CreatorId { get; set; } +} \ No newline at end of file diff --git a/src/Argon.Contracts/Models/Channel.cs b/src/Argon.Contracts/Models/Channel.cs new file mode 100644 index 00000000..fe930251 --- /dev/null +++ b/src/Argon.Contracts/Models/Channel.cs @@ -0,0 +1,24 @@ +namespace Argon.Contracts.Models; + +using System.ComponentModel.DataAnnotations; +using ArchetypeModel; +using MessagePack; +using Newtonsoft.Json; + +public record Channel : ArgonEntityWithOwnership, IArchetypeObject +{ + public ChannelType ChannelType { get; set; } + public Guid ServerId { get; set; } + [IgnoreMember, JsonIgnore] + public virtual Server Server { get; set; } + + + [MaxLength(128)] + public required string Name { get; set; } = string.Empty; + [MaxLength(1024)] + public string? Description { get; set; } = null; + + public virtual ICollection EntitlementOverwrites { get; set; } + = new List(); + public ICollection Overwrites => EntitlementOverwrites.OfType().ToList(); +} \ No newline at end of file diff --git a/src/Argon.Contracts/Models/ChannelEntitlementOverwrite.cs b/src/Argon.Contracts/Models/ChannelEntitlementOverwrite.cs new file mode 100644 index 00000000..2a407ad9 --- /dev/null +++ b/src/Argon.Contracts/Models/ChannelEntitlementOverwrite.cs @@ -0,0 +1,25 @@ +namespace Argon.Contracts.Models; + +using ArchetypeModel; +using MessagePack; + +[MessagePackObject(true)] +public record ChannelEntitlementOverwrite : ArgonEntityWithOwnership, IArchetypeOverwrite +{ + public Guid ChannelId { get; set; } + [IgnoreMember] + public virtual Channel Channel { get; set; } + + public IArchetypeScope Scope { get; set; } + + public Guid? ArchetypeId { get; set; } + [IgnoreMember] + public virtual Archetype Archetype { get; set; } + + public Guid? ServerMemberId { get; set; } + [IgnoreMember] + public virtual ServerMember ServerMember { get; set; } + + public ArgonEntitlement Allow { get; set; } + public ArgonEntitlement Deny { get; set; } +} \ No newline at end of file diff --git a/src/Argon.Contracts/Models/Server.cs b/src/Argon.Contracts/Models/Server.cs new file mode 100644 index 00000000..6815f538 --- /dev/null +++ b/src/Argon.Contracts/Models/Server.cs @@ -0,0 +1,23 @@ +namespace Argon.Contracts.Models; + +using ArchetypeModel; +using System.ComponentModel.DataAnnotations; + +public record Server : ArgonEntityWithOwnership, IArchetypeSubject +{ + public static readonly Guid DefaultSystemServer + = Guid.Parse("11111111-0000-1111-1111-111111111111"); + + [MaxLength(64)] + public string Name { get; set; } = string.Empty; + [MaxLength(1024)] + public string? Description { get; set; } = string.Empty; + [MaxLength(128)] + public string? AvatarFileId { get; set; } = string.Empty; + + + public virtual ICollection Channels { get; set; } = new List(); + public virtual ICollection Users { get; set; } = new List(); + public virtual ICollection Archetypes { get; set; } = new List(); + public ICollection SubjectArchetypes => Archetypes.OfType().ToList(); +} \ No newline at end of file diff --git a/src/Argon.Contracts/Models/ServerMember.cs b/src/Argon.Contracts/Models/ServerMember.cs new file mode 100644 index 00000000..13e4a07b --- /dev/null +++ b/src/Argon.Contracts/Models/ServerMember.cs @@ -0,0 +1,18 @@ +namespace Argon.Contracts.Models; + +using MessagePack; + +public record ServerMember : ArgonEntityWithOwnership +{ + public Guid ServerId { get; set; } + public Guid UserId { get; set; } + + public virtual User User { get; set; } + [IgnoreMember] + public virtual Server Server { get; set; } + + public DateTime JoinedAt { get; set; } + + public ICollection ServerMemberArchetypes { get; set; } + = new List(); +} \ No newline at end of file diff --git a/src/Argon.Contracts/Models/ServerMemberArchetype.cs b/src/Argon.Contracts/Models/ServerMemberArchetype.cs new file mode 100644 index 00000000..9e8c25af --- /dev/null +++ b/src/Argon.Contracts/Models/ServerMemberArchetype.cs @@ -0,0 +1,17 @@ +namespace Argon.Contracts.Models; + +using ArchetypeModel; +using MessagePack; + + +[MessagePackObject(true)] +public record ServerMemberArchetype +{ + public Guid ServerMemberId { get; set; } + public Guid ArchetypeId { get; set; } + + [IgnoreMember] + public virtual Archetype Archetype { get; set; } + [IgnoreMember] + public virtual ServerMember ServerMember { get; set; } +} \ No newline at end of file diff --git a/src/Argon.Contracts/Models/ShortUser.cs b/src/Argon.Contracts/Models/ShortUser.cs new file mode 100644 index 00000000..506727a1 --- /dev/null +++ b/src/Argon.Contracts/Models/ShortUser.cs @@ -0,0 +1,9 @@ +namespace Argon.Contracts.Models; + +using MessagePack; + +[MessagePackObject(true)] +public sealed record ShortUser(Guid userId, string Username, string DisplayName, string? AvatarFileId) +{ + public static implicit operator ShortUser(User user) => new(user.Id, user.Username, user.DisplayName, user.AvatarFileId); +} \ No newline at end of file diff --git a/src/Argon.Contracts/Models/User.cs b/src/Argon.Contracts/Models/User.cs new file mode 100644 index 00000000..7f89bd61 --- /dev/null +++ b/src/Argon.Contracts/Models/User.cs @@ -0,0 +1,29 @@ +namespace Argon.Contracts.Models; + +using MessagePack; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +[MessagePackObject(true)] +public sealed record User : ArgonEntity +{ + public static readonly Guid SystemUser + = Guid.Parse("11111111-2222-1111-2222-111111111111"); + + [MaxLength(255)] + public required string Email { get; set; } + [MaxLength(64)] + public required string Username { get; set; } + [MaxLength(64)] + public required string DisplayName { get; set; } + [MaxLength(64)] + public string? PhoneNumber { get; set; } = null; + [MaxLength(512), IgnoreMember, JsonIgnore] + public string? PasswordDigest { get; set; } = null; + [MaxLength(128)] + public string? AvatarFileId { get; set; } = null; + [MaxLength(128), IgnoreMember, JsonIgnore] + public string? OtpHash { get; set; } = null; + [IgnoreMember] + public ICollection ServerMembers { get; set; } = new List(); +} \ No newline at end of file