diff --git a/src/Argon.Api/Argon.Api.csproj b/src/Argon.Api/Argon.Api.csproj index e1d0b5d3..cfcf0e9e 100644 --- a/src/Argon.Api/Argon.Api.csproj +++ b/src/Argon.Api/Argon.Api.csproj @@ -11,6 +11,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Argon.Api/Controllers/UsersController.cs b/src/Argon.Api/Controllers/UsersController.cs index 4c0abbb0..9c2e35f9 100644 --- a/src/Argon.Api/Controllers/UsersController.cs +++ b/src/Argon.Api/Controllers/UsersController.cs @@ -28,7 +28,7 @@ public async Task> Post([FromBody] UserInputDto dto public async Task> Authenticate([FromBody] UserInputDto dto) { var userManager = grainFactory.GetGrain(dto.Username); - var token = await userManager.Authenticate(dto.Password); + var token = await userManager.Authorize(dto.Password); return token; } diff --git a/src/Argon.Api/Grains.Interfaces/IUserManager.cs b/src/Argon.Api/Grains.Interfaces/IUserManager.cs index 59991259..2829a54a 100644 --- a/src/Argon.Api/Grains.Interfaces/IUserManager.cs +++ b/src/Argon.Api/Grains.Interfaces/IUserManager.cs @@ -11,8 +11,8 @@ public interface IUserManager : IGrainWithStringKey [Alias("Get")] Task Get(); - [Alias("Authenticate")] - Task Authenticate(string password); + [Alias("Authorize")] + Task Authorize(string password); [Alias("CreateServer")] Task CreateServer(string name, string description); diff --git a/src/Argon.Api/Grains.Persistence.States/ChannelStorage.cs b/src/Argon.Api/Grains.Persistence.States/ChannelStorage.cs index 8bf7fbb1..52295b87 100644 --- a/src/Argon.Api/Grains.Persistence.States/ChannelStorage.cs +++ b/src/Argon.Api/Grains.Persistence.States/ChannelStorage.cs @@ -1,5 +1,6 @@ namespace Argon.Api.Grains.Persistence.States; +using Contracts; using MemoryPack; [GenerateSerializer] @@ -16,4 +17,18 @@ public sealed partial record ChannelStorage [Id(5)] public ServerRole AccessLevel { get; set; } = ServerRole.User; [Id(6)] public DateTime CreatedAt { get; } = DateTime.UtcNow; [Id(7)] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public static implicit operator ServerDetailsResponse(ChannelStorage channelStorage) + { + return new ServerDetailsResponse( + channelStorage.Id, + channelStorage.Name, + channelStorage.Description, + channelStorage.CreatedBy.ToString("N"), + channelStorage.ChannelType.ToString(), + channelStorage.AccessLevel.ToString(), + channelStorage.CreatedAt, + channelStorage.UpdatedAt + ); + } } \ No newline at end of file diff --git a/src/Argon.Api/Grains.Persistence.States/ServerStorage.cs b/src/Argon.Api/Grains.Persistence.States/ServerStorage.cs index d517a3e0..0f16794b 100644 --- a/src/Argon.Api/Grains.Persistence.States/ServerStorage.cs +++ b/src/Argon.Api/Grains.Persistence.States/ServerStorage.cs @@ -1,5 +1,6 @@ namespace Argon.Api.Grains.Persistence.States; +using Contracts; using MemoryPack; [GenerateSerializer] @@ -15,4 +16,17 @@ public sealed partial record ServerStorage [Id(6)] public List Channels { get; set; } = []; [Id(3)] public DateTime CreatedAt { get; } = DateTime.UtcNow; [Id(4)] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + + public static implicit operator ServerResponse(ServerStorage serverStorage) + { + return new ServerResponse( + serverStorage.Id, + serverStorage.Name, + serverStorage.Description, + serverStorage.AvatarUrl, + serverStorage.Channels, + serverStorage.CreatedAt, + serverStorage.UpdatedAt + ); + } } \ No newline at end of file diff --git a/src/Argon.Api/Grains.Persistence.States/UserStorage.cs b/src/Argon.Api/Grains.Persistence.States/UserStorage.cs index 09637aea..0f8eb863 100644 --- a/src/Argon.Api/Grains.Persistence.States/UserStorage.cs +++ b/src/Argon.Api/Grains.Persistence.States/UserStorage.cs @@ -1,6 +1,8 @@ namespace Argon.Api.Grains.Persistence.States; +using System.Runtime.Serialization; using MemoryPack; +using MessagePack; [GenerateSerializer] [Serializable] @@ -8,12 +10,41 @@ namespace Argon.Api.Grains.Persistence.States; [Alias(nameof(UserStorage))] public sealed partial record UserStorage { - [Id(0)] public Guid Id { get; set; } = Guid.Empty; - [Id(1)] public string Username { get; set; } = string.Empty; - [Id(2)] public string Password { get; set; } = string.Empty; - [Id(5)] public string AvatarUrl { get; set; } = string.Empty; - [Id(3)] public DateTime CreatedAt { get; } = DateTime.UtcNow; - [Id(4)] public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + [Id(0)] + public Guid Id { get; set; } = Guid.Empty; + + [property: DataMember(Order = 1)] + [property: MemoryPackOrder(1)] + [property: Key(1)] + [Id(1)] + public string Username { get; set; } = string.Empty; + + [property: DataMember(Order = 2)] + [property: MemoryPackOrder(2)] + [property: Key(2)] + [Id(2)] + public string Password { get; set; } = string.Empty; + + [property: DataMember(Order = 5)] + [property: MemoryPackOrder(5)] + [property: Key(5)] + [Id(5)] + public string AvatarUrl { get; set; } = string.Empty; + + [property: DataMember(Order = 3)] + [property: MemoryPackOrder(3)] + [property: Key(3)] + [Id(3)] + public DateTime CreatedAt { get; } = DateTime.UtcNow; + + [property: DataMember(Order = 4)] + [property: MemoryPackOrder(4)] + [property: Key(4)] + [Id(4)] + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public static implicit operator UserStorageDto(UserStorage userStorage) { diff --git a/src/Argon.Api/Grains.Persistence.States/UserStorageDto.cs b/src/Argon.Api/Grains.Persistence.States/UserStorageDto.cs index 72d03c47..32b44554 100644 --- a/src/Argon.Api/Grains.Persistence.States/UserStorageDto.cs +++ b/src/Argon.Api/Grains.Persistence.States/UserStorageDto.cs @@ -1,11 +1,14 @@ namespace Argon.Api.Grains.Persistence.States; +using Contracts; +using Mapster; using MemoryPack; [GenerateSerializer] [Serializable] [MemoryPackable] [Alias(nameof(UserStorageDto))] +[GenerateMapper] public sealed partial record UserStorageDto { [Id(0)] public Guid Id { get; set; } @@ -13,4 +16,15 @@ public sealed partial record UserStorageDto [Id(4)] public string AvatarUrl { get; set; } = string.Empty; [Id(2)] public DateTime CreatedAt { get; set; } [Id(3)] public DateTime UpdatedAt { get; set; } + + public static implicit operator UserResponse(UserStorageDto userStorageDto) + { + return new UserResponse( + userStorageDto.Id, + userStorageDto.Username, + userStorageDto.AvatarUrl, + userStorageDto.CreatedAt, + userStorageDto.UpdatedAt + ); + } } \ No newline at end of file diff --git a/src/Argon.Api/Grains/UserManager.cs b/src/Argon.Api/Grains/UserManager.cs index 6193ded6..0ec76647 100644 --- a/src/Argon.Api/Grains/UserManager.cs +++ b/src/Argon.Api/Grains/UserManager.cs @@ -36,7 +36,7 @@ public async Task Get() return userStore.State; } - public Task Authenticate(string password) + public Task Authorize(string password) { var match = userStore.State.Password == HashPassword(password); diff --git a/src/Argon.Api/Program.cs b/src/Argon.Api/Program.cs index d3d4bc06..343dfc02 100644 --- a/src/Argon.Api/Program.cs +++ b/src/Argon.Api/Program.cs @@ -18,6 +18,7 @@ builder.Services.AddControllers(opts => { opts.Filters.Add(); }); builder.Services.AddFusion(RpcServiceMode.Server, true) .Rpc.AddServer() + .AddServer() .AddWebSocketServer(true); builder.AddSwaggerWithAuthHeader(); builder.AddJwt(); diff --git a/src/Argon.Api/Services/UserAuthorization.cs b/src/Argon.Api/Services/UserAuthorization.cs index 03aac0ba..26141e82 100644 --- a/src/Argon.Api/Services/UserAuthorization.cs +++ b/src/Argon.Api/Services/UserAuthorization.cs @@ -1,14 +1,14 @@ namespace Argon.Api.Services; -using Grains.Interfaces; using Contracts; +using Grains.Interfaces; public class UserAuthorization(IGrainFactory grainFactory) : IUserAuthorization { public async Task AuthorizeAsync(AuthorizeRequest request) { // TODO machineKey - var token = await grainFactory.GetGrain(request.username).Authenticate(request.password); - return new AuthorizeResponse(token, [new ServerResponse(Guid.NewGuid(), "xuita", null)]); + var token = await grainFactory.GetGrain(request.username).Authorize(request.password); + return new AuthorizeResponse(token); } } \ No newline at end of file diff --git a/src/Argon.Api/Services/UserInteractionService.cs b/src/Argon.Api/Services/UserInteractionService.cs new file mode 100644 index 00000000..85798eb6 --- /dev/null +++ b/src/Argon.Api/Services/UserInteractionService.cs @@ -0,0 +1,41 @@ +namespace Argon.Api.Services; + +using Contracts; +using Grains.Interfaces; + +public class UserInteractionService( + string username, // TODO to be injected + IGrainFactory grainFactory +) : IUserInteraction +{ + private readonly IUserManager userManager = grainFactory.GetGrain(username); + + public async Task GetMe() + { + return await userManager.Get(); + } + + public async Task CreateServer(CreateServerRequest request) + { + return await userManager.CreateServer(request.Name, request.Description); + } + + public async Task> GetServers() + { + return (await userManager.GetServers()) + .Select(x => (ServerResponse)x) + .ToList(); + } + + public async Task> GetServerDetails(ServerDetailsRequest request) + { + return (await userManager.GetServerChannels(request.ServerId)) + .Select(x => (ServerDetailsResponse)x) + .ToList(); + } + + public async Task JoinChannel(ChannelJoinRequest request) + { + return new ChannelJoinResponse((await userManager.JoinChannel(request.ServerId, request.ChannelId)).value); + } +} \ No newline at end of file diff --git a/src/Argon.Contracts/Argon.Contracts.csproj b/src/Argon.Contracts/Argon.Contracts.csproj index cd6dcee5..6f90131f 100644 --- a/src/Argon.Contracts/Argon.Contracts.csproj +++ b/src/Argon.Contracts/Argon.Contracts.csproj @@ -1,22 +1,23 @@  - - net8.0 - enable - enable - + + net8.0 + enable + enable + - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/Argon.Contracts/IUserAuthorization.cs b/src/Argon.Contracts/IUserAuthorization.cs index abcda690..0404cf4d 100644 --- a/src/Argon.Contracts/IUserAuthorization.cs +++ b/src/Argon.Contracts/IUserAuthorization.cs @@ -1,7 +1,6 @@ namespace Argon.Contracts; using System.Runtime.Serialization; -using ActualLab.Fusion; using ActualLab.Rpc; using MemoryPack; using MessagePack; @@ -11,19 +10,25 @@ public interface IUserAuthorization : IRpcService Task AuthorizeAsync(AuthorizeRequest request); } -[DataContract, MemoryPackable(GenerateType.VersionTolerant), MessagePackObject] +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] public sealed partial record AuthorizeRequest( - [property: DataMember(Order = 0), MemoryPackOrder(0), Key(0)] string username, - [property: DataMember(Order = 1), MemoryPackOrder(1), Key(1)] string password, - [property: DataMember(Order = 2), MemoryPackOrder(2), Key(2)] string machineKey); + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + string username, + [property: DataMember(Order = 1)] + [property: MemoryPackOrder(1)] + [property: Key(1)] + string password, + [property: DataMember(Order = 2)] + [property: MemoryPackOrder(2)] + [property: Key(2)] + string machineKey); - -public sealed partial record ServerResponse( - [property: DataMember(Order = 0), MemoryPackOrder(0), Key(0)] Guid serverId, - [property: DataMember(Order = 1), MemoryPackOrder(1), Key(1)] string serverName, - [property: DataMember(Order = 2), MemoryPackOrder(2), Key(2)] string? avatarFileId -); - -public sealed partial record AuthorizeResponse( - [property: DataMember(Order = 0), MemoryPackOrder(0), Key(0)] string token, - [property: DataMember(Order = 1), MemoryPackOrder(1), Key(1)] List servers); \ No newline at end of file +public sealed record AuthorizeResponse( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + string token); \ No newline at end of file diff --git a/src/Argon.Contracts/IUserInteraction.cs b/src/Argon.Contracts/IUserInteraction.cs new file mode 100644 index 00000000..39fad3dc --- /dev/null +++ b/src/Argon.Contracts/IUserInteraction.cs @@ -0,0 +1,170 @@ +namespace Argon.Contracts; + +using System.Runtime.Serialization; +using ActualLab.Rpc; +using MemoryPack; +using MessagePack; + +public interface IUserInteraction : IRpcService +{ + Task GetMe(); + Task CreateServer(CreateServerRequest request); + Task> GetServers(); + + Task> GetServerDetails(ServerDetailsRequest request); + + // Task CreateChannel(string username); + Task JoinChannel(ChannelJoinRequest request); +} + +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] +public sealed partial record ServerDetailsRequest( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + Guid ServerId +); + +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] +public sealed partial record UserResponse( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + Guid Id, + [property: DataMember(Order = 1)] + [property: MemoryPackOrder(1)] + [property: Key(1)] + string Username, + [property: DataMember(Order = 2)] + [property: MemoryPackOrder(2)] + [property: Key(2)] + string AvatarUrl, + [property: DataMember(Order = 3)] + [property: MemoryPackOrder(3)] + [property: Key(3)] + DateTime CreatedAt, + [property: DataMember(Order = 4)] + [property: MemoryPackOrder(4)] + [property: Key(4)] + DateTime UpdatedAt +); + +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] +public sealed partial record CreateServerRequest( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + string Name, + [property: DataMember(Order = 1)] + [property: MemoryPackOrder(1)] + [property: Key(1)] + string Description, + [property: DataMember(Order = 2)] + [property: MemoryPackOrder(2)] + [property: Key(2)] + string AvatarUrl +); + +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] +public sealed partial record ServerResponse( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + Guid Id, + [property: DataMember(Order = 1)] + [property: MemoryPackOrder(1)] + [property: Key(1)] + string Name, + [property: DataMember(Order = 2)] + [property: MemoryPackOrder(2)] + [property: Key(2)] + string Description, + [property: DataMember(Order = 3)] + [property: MemoryPackOrder(3)] + [property: Key(3)] + string AvatarUrl, + [property: DataMember(Order = 4)] + [property: MemoryPackOrder(4)] + [property: Key(4)] + List Channels, + [property: DataMember(Order = 5)] + [property: MemoryPackOrder(5)] + [property: Key(5)] + DateTime CreatedAt, + [property: DataMember(Order = 6)] + [property: MemoryPackOrder(6)] + [property: Key(6)] + DateTime UpdatedAt + // TODO: all users of the server with their statuses(online/offline/in channel) +); + +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] +public sealed partial record ServerDetailsResponse( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + Guid Id, + [property: DataMember(Order = 1)] + [property: MemoryPackOrder(1)] + [property: Key(1)] + string Name, + [property: DataMember(Order = 2)] + [property: MemoryPackOrder(2)] + [property: Key(2)] + string Description, + [property: DataMember(Order = 3)] + [property: MemoryPackOrder(3)] + [property: Key(3)] + string CreatedBy, + [property: DataMember(Order = 4)] + [property: MemoryPackOrder(4)] + [property: Key(4)] + string ChannelType, + [property: DataMember(Order = 5)] + [property: MemoryPackOrder(5)] + [property: Key(5)] + string AccessLevel, + [property: DataMember(Order = 6)] + [property: MemoryPackOrder(6)] + [property: Key(6)] + DateTime CreatedAt, + [property: DataMember(Order = 7)] + [property: MemoryPackOrder(7)] + [property: Key(7)] + DateTime UpdatedAt + // TODO: all users currently connected to this channel +); + +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] +public sealed partial record ChannelJoinRequest( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + Guid ServerId, + [property: DataMember(Order = 1)] + [property: MemoryPackOrder(1)] + [property: Key(1)] + Guid ChannelId +); + +[DataContract] +[MemoryPackable(GenerateType.VersionTolerant)] +[MessagePackObject] +public sealed partial record ChannelJoinResponse( + [property: DataMember(Order = 0)] + [property: MemoryPackOrder(0)] + [property: Key(0)] + string Token +); \ No newline at end of file