Skip to content

Commit

Permalink
Merge pull request #9 from argon-chat/feature/servers
Browse files Browse the repository at this point in the history
Server related grains
  • Loading branch information
0xF6 authored Oct 27, 2024
2 parents 3227f97 + 0635302 commit 19dd243
Show file tree
Hide file tree
Showing 25 changed files with 532 additions and 48 deletions.
36 changes: 18 additions & 18 deletions src/Argon.Api/Argon.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ActualLab.Fusion" Version="9.5.52" />
<PackageReference Include="ActualLab.Fusion.EntityFramework.Npgsql" Version="9.5.52" />
<PackageReference Include="ActualLab.Rpc.Server" Version="9.5.52" />
<PackageReference Include="Argon.Sfu.Protocol" Version="1.26.0" />
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Flurl.Http" Version="4.0.2" />
<PackageReference Include="Flurl.Http.Newtonsoft" Version="0.9.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Flurl.Http" Version="4.0.2"/>
<PackageReference Include="Flurl.Http.Newtonsoft" Version="0.9.1"/>
<PackageReference Include="MemoryPack" Version="1.21.3"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10"/>
<PackageReference Include="ActualLab.Fusion" Version="9.5.52"/>
<PackageReference Include="ActualLab.Fusion.EntityFramework.Npgsql" Version="9.5.52"/>
<PackageReference Include="ActualLab.Rpc.Server" Version="9.5.52"/>
<PackageReference Include="Argon.Sfu.Protocol" Version="1.26.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Orleans.Persistence.AdoNet" Version="8.2.0" />
<PackageReference Include="Microsoft.Orleans.Runtime" Version="8.2.0" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.2.0" />
<PackageReference Include="Microsoft.Orleans.Server" Version="8.2.0" />
<PackageReference Include="Npgsql" Version="8.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />

<PackageReference Include="Microsoft.Orleans.Persistence.AdoNet" Version="8.2.0"/>
<PackageReference Include="Microsoft.Orleans.Runtime" Version="8.2.0"/>
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.2.0"/>
<PackageReference Include="Microsoft.Orleans.Serialization" Version="8.2.0"/>
<PackageReference Include="Microsoft.Orleans.Server" Version="8.2.0"/>
<PackageReference Include="Npgsql" Version="8.0.5"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Argon.Contracts\Argon.Contracts.csproj" />
<ProjectReference Include="..\ServiceDefaults\ServiceDefaults.csproj" />
<ProjectReference Include="..\Argon.Contracts\Argon.Contracts.csproj"/>
<ProjectReference Include="..\ServiceDefaults\ServiceDefaults.csproj"/>
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions src/Argon.Api/Attributes/InjectUsernameAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Argon.Api.Attributes;

[AttributeUsage(AttributeTargets.Method)]
public class InjectUsernameAttribute : Attribute
{
}
59 changes: 55 additions & 4 deletions src/Argon.Api/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
namespace Argon.Api.Controllers;

using Attributes;
using Grains.Interfaces;
using Grains.Persistence.States;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Sfu;

#if DEBUG
public record UserInputDto(string Username, string Password);

public record ServerInputDto(
string Name,
string Description);

[Route("api/[controller]")]
public class UsersController(IGrainFactory grainFactory, ILogger<UsersController> logger) : ControllerBase
{
Expand All @@ -28,19 +34,64 @@ public async Task<ActionResult<string>> Authenticate([FromBody] UserInputDto dto

[HttpGet]
[Authorize]
public async Task<ActionResult<UserStorageDto>> Get()
[InjectUsername]
public async Task<ActionResult<UserStorageDto>> Get(string username)
{
var username = User.Claims.FirstOrDefault(cl => cl.Type == "username")?.Value;
var userManager = grainFactory.GetGrain<IUserManager>(username);
return await userManager.Get();
}

[HttpGet("{username}")]
[Authorize]
public async Task<ActionResult<UserStorageDto>> Get(string username)
public async Task<ActionResult<UserStorageDto>> GetByUsername(string username)
{
var userManager = grainFactory.GetGrain<IUserManager>(username);
return await userManager.Get();
}

[HttpPost("server")]
[Authorize]
[InjectUsername]
public async Task<ActionResult<ServerStorage>> CreateServer(string username, [FromBody] ServerInputDto dto)
{
var userManager = grainFactory.GetGrain<IUserManager>(username);
return await userManager.CreateServer(dto.Name, dto.Description);
}

[HttpGet("servers")]
[Authorize]
[InjectUsername]
public async Task<ActionResult<List<ServerStorage>>> GetServers(string username)
{
var userManager = grainFactory.GetGrain<IUserManager>(username);
return await userManager.GetServers();
}

[HttpGet("servers/{serverId}/channels")]
[Authorize]
[InjectUsername]
public async Task<ActionResult<IEnumerable<ChannelStorage>>> GetServerChannels(string username, Guid serverId)
{
var userManager = grainFactory.GetGrain<IUserManager>(username);
var channels = await userManager.GetServerChannels(serverId);
return channels.ToList();
}

[HttpGet("servers/{serverId}/channels/{channelId}")]
[Authorize]
[InjectUsername]
public async Task<ActionResult<ChannelStorage>> GetChannel(string username, Guid serverId, Guid channelId)
{
var userManager = grainFactory.GetGrain<IUserManager>(username);
return await userManager.GetChannel(serverId, channelId);
}

[HttpPost("servers/{serverId}/channels/{channelId}/join")]
[Authorize]
[InjectUsername]
public async Task<ActionResult<RealtimeToken>> JoinChannel(string username, Guid serverId, Guid channelId)
{
var userManager = grainFactory.GetGrain<IUserManager>(username);
return await userManager.JoinChannel(serverId, channelId);
}
}
#endif
17 changes: 17 additions & 0 deletions src/Argon.Api/Extensions/OrleansExtension.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
namespace Argon.Api.Extensions;

using MemoryPack;
using Orleans.Configuration;
using Orleans.Storage;

internal class MemoryPackStorageSerializer : IGrainStorageSerializer
{
public BinaryData Serialize<T>(T input)
{
return new BinaryData(MemoryPackSerializer.Serialize(input));
}

public T Deserialize<T>(BinaryData input)
{
return MemoryPackSerializer.Deserialize<T>(input) ?? throw new InvalidOperationException();
}
}

public static class OrleansExtension
{
Expand All @@ -18,7 +33,9 @@ public static WebApplicationBuilder AddOrleans(this WebApplicationBuilder builde
{
options.Invariant = "Npgsql";
options.ConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
options.GrainStorageSerializer = new MemoryPackStorageSerializer();
})
.AddMemoryGrainStorageAsDefault()
.UseLocalhostClustering();
});

Expand Down
17 changes: 14 additions & 3 deletions src/Argon.Api/Features/Sfu/Models.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
namespace Argon.Sfu;

using LiveKit.Proto;
using MemoryPack;

public record struct EphemeralChannelInfo(ArgonChannelId channelId, string sid, Room room);

public record struct RealtimeToken(string value);
[Serializable]
[GenerateSerializer]
[MemoryPackable]
[Alias(nameof(RealtimeToken))]
public partial record struct RealtimeToken(string value);

public record struct ArgonUserId(Guid id)
{
public string ToRawIdentity() => id.ToString("N");
public string ToRawIdentity()
{
return id.ToString("N");
}
}

public record struct ArgonServerId(Guid id);

public record struct ArgonChannelId(ArgonServerId serverId, Guid channelId)
{
public string ToRawRoomId() => $"{serverId.id:N}:{channelId:N}";
public string ToRawRoomId()
{
return $"{serverId.id:N}:{channelId:N}";
}
}
17 changes: 17 additions & 0 deletions src/Argon.Api/Filters/InjectUsernameFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Argon.Api.Filters;

using Microsoft.AspNetCore.Mvc.Filters;

public class InjectUsernameFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var username = context.HttpContext.User.Claims.FirstOrDefault(cl => cl.Type == "username")?.Value;
if (!string.IsNullOrWhiteSpace(username))
context.ActionArguments["username"] = username;
}

public void OnActionExecuted(ActionExecutedContext context)
{
}
}
19 changes: 19 additions & 0 deletions src/Argon.Api/Grains.Interfaces/IChannelManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Argon.Api.Grains.Interfaces;

using Persistence.States;
using Sfu;

public interface IChannelManager : IGrainWithGuidKey
{
[Alias("CreateChannel")]
Task<ChannelStorage> CreateChannel(ChannelStorage channel);

[Alias("GetChannel")]
Task<ChannelStorage> GetChannel();

[Alias("JoinLink")]
Task<RealtimeToken> JoinLink(Guid userId, Guid serverId);

[Alias("UpdateChannel")]
Task<ChannelStorage> UpdateChannel(ChannelStorage channel);
}
31 changes: 31 additions & 0 deletions src/Argon.Api/Grains.Interfaces/IServerManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Argon.Api.Grains.Interfaces;

using Persistence.States;
using Sfu;

public interface IServerManager : IGrainWithGuidKey
{
[Alias("CreateServer")]
Task<ServerStorage> CreateServer(string name, string description, Guid userId);

[Alias("CreateJoinLink")]
Task<string> CreateJoinLink();

[Alias("AddUser")]
Task AddUser(UserToServerRelation Relation);

[Alias("GetChannels")]
Task<IEnumerable<ChannelStorage>> GetChannels();

[Alias("AddChannel")]
Task<ChannelStorage> AddChannel(ChannelStorage channel);

[Alias("GetChannel")]
Task<ChannelStorage> GetChannel(Guid channelId);

[Alias("GetServer")]
Task<ServerStorage> GetServer();

[Alias("JoinChannel")]
Task<RealtimeToken> JoinChannel(Guid userId, Guid channelId);
}
16 changes: 16 additions & 0 deletions src/Argon.Api/Grains.Interfaces/IUserManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Argon.Api.Grains.Interfaces;

using Persistence.States;
using Sfu;

public interface IUserManager : IGrainWithStringKey
{
Expand All @@ -12,4 +13,19 @@ public interface IUserManager : IGrainWithStringKey

[Alias("Authenticate")]
Task<string> Authenticate(string password);

[Alias("CreateServer")]
Task<ServerStorage> CreateServer(string name, string description);

[Alias("GetServers")]
Task<List<ServerStorage>> GetServers();

[Alias("GetServerChannels")]
Task<IEnumerable<ChannelStorage>> GetServerChannels(Guid serverId);

[Alias("GetChannel")]
Task<ChannelStorage> GetChannel(Guid serverId, Guid channelId);

[Alias("JoinChannel")]
Task<RealtimeToken> JoinChannel(Guid serverId, Guid channelId);
}
19 changes: 19 additions & 0 deletions src/Argon.Api/Grains.Persistence.States/ChannelStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Argon.Api.Grains.Persistence.States;

using MemoryPack;

[GenerateSerializer]
[Serializable]
[MemoryPackable]
[Alias(nameof(ChannelStorage))]
public sealed partial record ChannelStorage
{
[Id(0)] public Guid Id { get; set; } = Guid.Empty;
[Id(1)] public string Name { get; set; } = string.Empty;
[Id(2)] public string Description { get; set; } = string.Empty;
[Id(3)] public Guid CreatedBy { get; set; } = Guid.Empty;
[Id(4)] public ChannelType ChannelType { get; set; } = ChannelType.Text;
[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;
}
11 changes: 11 additions & 0 deletions src/Argon.Api/Grains.Persistence.States/ChannelType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Argon.Api.Grains.Persistence.States;

[GenerateSerializer]
[Serializable]
[Alias(nameof(ChannelType))]
public enum ChannelType
{
Text,
Voice,
Announcement
}
5 changes: 4 additions & 1 deletion src/Argon.Api/Grains.Persistence.States/HelloArchive.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace Argon.Api.Grains.Persistence.States;

using MemoryPack;

[GenerateSerializer]
[MemoryPackable]
[Alias(nameof(HelloArchive))]
public sealed record class HelloArchive
public sealed partial record HelloArchive
{
[Id(0)] public List<string> Hellos { get; private set; } = new();
[Id(1)] public List<int> Ints { get; private set; } = new();
Expand Down
12 changes: 12 additions & 0 deletions src/Argon.Api/Grains.Persistence.States/ServerChannelsStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Argon.Api.Grains.Persistence.States;

using MemoryPack;

[GenerateSerializer]
[Serializable]
[MemoryPackable]
[Alias(nameof(ServerChannelsStore))]
public sealed partial record ServerChannelsStore
{
[Id(0)] public List<Guid> Channels { get; private set; } = [];
}
11 changes: 11 additions & 0 deletions src/Argon.Api/Grains.Persistence.States/ServerRole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Argon.Api.Grains.Persistence.States;

[GenerateSerializer]
[Serializable]
[Alias(nameof(ServerRole))]
public enum ServerRole : ushort // TODO: sort out roles and how we actually want to handle them
{
User,
Admin,
Owner
}
Loading

0 comments on commit 19dd243

Please sign in to comment.