From 1ec4450040fff28eea0482a161123644f7b4a715 Mon Sep 17 00:00:00 2001 From: ChiaraBm Date: Thu, 5 Jun 2025 23:35:39 +0200 Subject: [PATCH] Started implementing server share backend. Redesigned server authorization for api calls. Refactored controller names for servers. Moved some responses to correct namespace --- .../Database/Entities/Server.cs | 1 + .../Database/Entities/ServerShare.cs | 20 + ...250605210823_AddedServerShares.Designer.cs | 500 ++++++++++++++++++ .../20250605210823_AddedServerShares.cs | 51 ++ .../ServersDataContextModelSnapshot.cs | 46 +- .../Database/ServersDataContext.cs | 1 + ...SystemController.cs => FilesController.cs} | 48 +- ...rPowerController.cs => PowerController.cs} | 24 +- .../Controllers/Client/ServersController.cs | 152 ++++-- .../Controllers/Client/SettingsController.cs | 55 ++ .../Controllers/Client/SharesController.cs | 12 + ...esController.cs => VariablesController.cs} | 58 +- .../Models/ServerSharePermission.cs | 9 + .../Services/ServerAuthorizeService.cs | 129 +++++ .../Services/ServerService.cs | 13 + .../Controllers/Servers/ServersController.cs | 21 + .../ServerSystem/SubSystems/StatsSubSystem.cs | 29 +- .../SubSystems/StorageSubSystem.cs | 24 + .../Services/ServerService.cs | 4 +- .../Responses/Servers/ServerStatsResponse.cs | 11 + .../DefaultServerTabProvider.cs | 2 +- .../Interfaces/IServerTabProvider.cs | 2 +- .../Services/ServerService.cs | 9 +- .../UI/Components/Servers/ServerCard.razor | 9 +- .../Servers/ServerTabs/BaseServerTab.razor | 2 +- .../UI/Components/Servers/TestServerCrd.razor | 2 +- .../UI/Views/Client/Index.razor | 25 +- .../UI/Views/Client/Manage.razor | 2 +- .../Enums/ServerPermissionType.cs | 7 + .../Allocations/AllocationDetailResponse.cs | 2 +- .../Servers/ServerDetailResponse.cs | 8 +- .../Servers/ServerLogsResponse.cs | 2 +- .../Client/Servers/ServerStatsResponse.cs | 11 + .../Servers/ServerStatusResponse.cs | 2 +- .../Servers/ServerWebSocketResponse.cs | 2 +- .../Servers/Shares/ServerShareResponse.cs | 9 + .../MoonlightServers.Shared.csproj | 4 + 37 files changed, 1169 insertions(+), 139 deletions(-) create mode 100644 MoonlightServers.ApiServer/Database/Entities/ServerShare.cs create mode 100644 MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.Designer.cs create mode 100644 MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.cs rename MoonlightServers.ApiServer/Http/Controllers/Client/{ServerFileSystemController.cs => FilesController.cs} (80%) rename MoonlightServers.ApiServer/Http/Controllers/Client/{ServerPowerController.cs => PowerController.cs} (76%) create mode 100644 MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs create mode 100644 MoonlightServers.ApiServer/Http/Controllers/Client/SharesController.cs rename MoonlightServers.ApiServer/Http/Controllers/Client/{ServerVariablesController.cs => VariablesController.cs} (74%) create mode 100644 MoonlightServers.ApiServer/Models/ServerSharePermission.cs create mode 100644 MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs create mode 100644 MoonlightServers.DaemonShared/DaemonSide/Http/Responses/Servers/ServerStatsResponse.cs create mode 100644 MoonlightServers.Shared/Enums/ServerPermissionType.cs rename MoonlightServers.Shared/Http/Responses/{Users => Client/Servers}/Allocations/AllocationDetailResponse.cs (66%) rename MoonlightServers.Shared/Http/Responses/{Users => Client}/Servers/ServerDetailResponse.cs (51%) rename MoonlightServers.Shared/Http/Responses/{Users => Client}/Servers/ServerLogsResponse.cs (54%) create mode 100644 MoonlightServers.Shared/Http/Responses/Client/Servers/ServerStatsResponse.cs rename MoonlightServers.Shared/Http/Responses/{Users => Client}/Servers/ServerStatusResponse.cs (64%) rename MoonlightServers.Shared/Http/Responses/{Users => Client}/Servers/ServerWebSocketResponse.cs (65%) create mode 100644 MoonlightServers.Shared/Http/Responses/Client/Servers/Shares/ServerShareResponse.cs diff --git a/MoonlightServers.ApiServer/Database/Entities/Server.cs b/MoonlightServers.ApiServer/Database/Entities/Server.cs index 97f2232..66feafb 100644 --- a/MoonlightServers.ApiServer/Database/Entities/Server.cs +++ b/MoonlightServers.ApiServer/Database/Entities/Server.cs @@ -10,6 +10,7 @@ public class Server public List Allocations { get; set; } = new(); public List Variables { get; set; } = new(); public List Backups { get; set; } = new(); + public List Shares { get; set; } = new(); // Meta public string Name { get; set; } diff --git a/MoonlightServers.ApiServer/Database/Entities/ServerShare.cs b/MoonlightServers.ApiServer/Database/Entities/ServerShare.cs new file mode 100644 index 0000000..4955f81 --- /dev/null +++ b/MoonlightServers.ApiServer/Database/Entities/ServerShare.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace MoonlightServers.ApiServer.Database.Entities; + +public class ServerShare +{ + public int Id { get; set; } + + public int UserId { get; set; } + public Server Server { get; set; } + + [Column(TypeName = "jsonb")] + public string Permissions { get; set; } + + [Column(TypeName="timestamp with time zone")] + public DateTime CreatedAt { get; set; } + + [Column(TypeName="timestamp with time zone")] + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.Designer.cs b/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.Designer.cs new file mode 100644 index 0000000..8abcf77 --- /dev/null +++ b/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.Designer.cs @@ -0,0 +1,500 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MoonlightServers.ApiServer.Database; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MoonlightServers.ApiServer.Database.Migrations +{ + [DbContext(typeof(ServersDataContext))] + [Migration("20250605210823_AddedServerShares")] + partial class AddedServerShares + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("NodeId") + .HasColumnType("integer"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ServerId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("Servers_Allocations", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EnableDynamicFirewall") + .HasColumnType("boolean"); + + b.Property("EnableTransparentMode") + .HasColumnType("boolean"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("text"); + + b.Property("FtpPort") + .HasColumnType("integer"); + + b.Property("HttpPort") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("text"); + + b.Property("UseSsl") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Servers_Nodes", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Bandwidth") + .HasColumnType("integer"); + + b.Property("Cpu") + .HasColumnType("integer"); + + b.Property("Disk") + .HasColumnType("integer"); + + b.Property("DockerImageIndex") + .HasColumnType("integer"); + + b.Property("Memory") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NodeId") + .HasColumnType("integer"); + + b.Property("OwnerId") + .HasColumnType("integer"); + + b.Property("StarId") + .HasColumnType("integer"); + + b.Property("StartupOverride") + .HasColumnType("text"); + + b.Property("UseVirtualDisk") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("StarId"); + + b.ToTable("Servers_Servers", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Completed") + .HasColumnType("boolean"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ServerId") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Successful") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("Servers_ServerBackups", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ServerId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("Servers_ServerShares", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("ServerId") + .HasColumnType("integer"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("Servers_ServerVariables", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowDockerImageChange") + .HasColumnType("boolean"); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("DefaultDockerImage") + .HasColumnType("integer"); + + b.Property("DonateUrl") + .HasColumnType("text"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("text"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("text"); + + b.Property("InstallShell") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OnlineDetection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ParseConfiguration") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequiredAllocations") + .HasColumnType("integer"); + + b.Property("StartupCommand") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdateUrl") + .HasColumnType("text"); + + b.Property("Version") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Servers_Stars", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoPulling") + .HasColumnType("boolean"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("text"); + + b.Property("StarId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("StarId"); + + b.ToTable("Servers_StarDockerImages", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowEditing") + .HasColumnType("boolean"); + + b.Property("AllowViewing") + .HasColumnType("boolean"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("Filter") + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("StarId") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("StarId"); + + b.ToTable("Servers_StarVariables", (string)null); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node") + .WithMany("Allocations") + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server") + .WithMany("Allocations") + .HasForeignKey("ServerId"); + + b.Navigation("Node"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node") + .WithMany("Servers") + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star") + .WithMany() + .HasForeignKey("StarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + + b.Navigation("Star"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server") + .WithMany("Shares") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server") + .WithMany("Variables") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star") + .WithMany("DockerImages") + .HasForeignKey("StarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Star"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star") + .WithMany("Variables") + .HasForeignKey("StarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Star"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + + b.Navigation("Servers"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Shares"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.cs b/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.cs new file mode 100644 index 0000000..25d4cfc --- /dev/null +++ b/MoonlightServers.ApiServer/Database/Migrations/20250605210823_AddedServerShares.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MoonlightServers.ApiServer.Database.Migrations +{ + /// + public partial class AddedServerShares : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Servers_ServerShares", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "integer", nullable: false), + ServerId = table.Column(type: "integer", nullable: false), + Permissions = table.Column(type: "jsonb", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Servers_ServerShares", x => x.Id); + table.ForeignKey( + name: "FK_Servers_ServerShares_Servers_Servers_ServerId", + column: x => x.ServerId, + principalTable: "Servers_Servers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Servers_ServerShares_ServerId", + table: "Servers_ServerShares", + column: "ServerId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Servers_ServerShares"); + } + } +} diff --git a/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs b/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs index 98becbc..9136078 100644 --- a/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs +++ b/MoonlightServers.ApiServer/Database/Migrations/ServersDataContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace MoonlightServers.ApiServer.Database.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("ProductVersion", "9.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -180,6 +180,37 @@ namespace MoonlightServers.ApiServer.Database.Migrations b.ToTable("Servers_ServerBackups", (string)null); }); + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("ServerId") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("Servers_ServerShares", (string)null); + }); + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b => { b.Property("Id") @@ -392,6 +423,17 @@ namespace MoonlightServers.ApiServer.Database.Migrations .HasForeignKey("ServerId"); }); + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b => + { + b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server") + .WithMany("Shares") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b => { b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server") @@ -438,6 +480,8 @@ namespace MoonlightServers.ApiServer.Database.Migrations b.Navigation("Backups"); + b.Navigation("Shares"); + b.Navigation("Variables"); }); diff --git a/MoonlightServers.ApiServer/Database/ServersDataContext.cs b/MoonlightServers.ApiServer/Database/ServersDataContext.cs index 6e67b70..b34fbd4 100644 --- a/MoonlightServers.ApiServer/Database/ServersDataContext.cs +++ b/MoonlightServers.ApiServer/Database/ServersDataContext.cs @@ -13,6 +13,7 @@ public class ServersDataContext : DatabaseContext public DbSet Nodes { get; set; } public DbSet Servers { get; set; } public DbSet ServerBackups { get; set; } + public DbSet ServerShares { get; set; } public DbSet ServerVariables { get; set; } public DbSet Stars { get; set; } public DbSet StarDockerImages { get; set; } diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/FilesController.cs similarity index 80% rename from MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs rename to MoonlightServers.ApiServer/Http/Controllers/Client/FilesController.cs index c717952..9a13198 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerFileSystemController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/FilesController.cs @@ -7,6 +7,7 @@ using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Services; using MoonlightServers.DaemonShared.Enums; +using MoonlightServers.Shared.Enums; using MoonlightServers.Shared.Http.Requests.Client.Servers.Files; using MoonlightServers.Shared.Http.Responses.Client.Servers.Files; @@ -15,33 +16,30 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Client; [Authorize] [ApiController] [Route("api/client/servers")] -public class ServerFileSystemController : Controller +public class FilesController : Controller { private readonly DatabaseRepository ServerRepository; - private readonly DatabaseRepository UserRepository; private readonly ServerFileSystemService ServerFileSystemService; - private readonly ServerService ServerService; private readonly NodeService NodeService; + private readonly ServerAuthorizeService AuthorizeService; - public ServerFileSystemController( + public FilesController( DatabaseRepository serverRepository, - DatabaseRepository userRepository, ServerFileSystemService serverFileSystemService, - ServerService serverService, - NodeService nodeService + NodeService nodeService, + ServerAuthorizeService authorizeService ) { ServerRepository = serverRepository; - UserRepository = userRepository; ServerFileSystemService = serverFileSystemService; - ServerService = serverService; NodeService = nodeService; + AuthorizeService = authorizeService; } [HttpGet("{serverId:int}/files/list")] public async Task List([FromRoute] int serverId, [FromQuery] string path) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.Read); var entries = await ServerFileSystemService.List(server, path); @@ -58,7 +56,7 @@ public class ServerFileSystemController : Controller [HttpPost("{serverId:int}/files/move")] public async Task Move([FromRoute] int serverId, [FromQuery] string oldPath, [FromQuery] string newPath) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); await ServerFileSystemService.Move(server, oldPath, newPath); } @@ -66,7 +64,7 @@ public class ServerFileSystemController : Controller [HttpDelete("{serverId:int}/files/delete")] public async Task Delete([FromRoute] int serverId, [FromQuery] string path) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); await ServerFileSystemService.Delete(server, path); } @@ -74,7 +72,7 @@ public class ServerFileSystemController : Controller [HttpPost("{serverId:int}/files/mkdir")] public async Task Mkdir([FromRoute] int serverId, [FromQuery] string path) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); await ServerFileSystemService.Mkdir(server, path); } @@ -82,7 +80,7 @@ public class ServerFileSystemController : Controller [HttpGet("{serverId:int}/files/upload")] public async Task Upload([FromRoute] int serverId) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); var accessToken = NodeService.CreateAccessToken( server.Node, @@ -93,7 +91,7 @@ public class ServerFileSystemController : Controller }, TimeSpan.FromMinutes(1) ); - + var url = ""; url += server.Node.UseSsl ? "https://" : "http://"; @@ -105,11 +103,11 @@ public class ServerFileSystemController : Controller UploadUrl = url }; } - + [HttpGet("{serverId:int}/files/download")] public async Task Download([FromRoute] int serverId, [FromQuery] string path) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.Read); var accessToken = NodeService.CreateAccessToken( server.Node, @@ -121,7 +119,7 @@ public class ServerFileSystemController : Controller }, TimeSpan.FromMinutes(1) ); - + var url = ""; url += server.Node.UseSsl ? "https://" : "http://"; @@ -137,18 +135,18 @@ public class ServerFileSystemController : Controller [HttpPost("{serverId:int}/files/compress")] public async Task Compress([FromRoute] int serverId, [FromBody] ServerFilesCompressRequest request) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); if (!Enum.TryParse(request.Type, true, out CompressType type)) throw new HttpApiException("Invalid compress type provided", 400); await ServerFileSystemService.Compress(server, type, request.Items, request.Destination); } - + [HttpPost("{serverId:int}/files/decompress")] public async Task Decompress([FromRoute] int serverId, [FromBody] ServerFilesDecompressRequest request) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); if (!Enum.TryParse(request.Type, true, out CompressType type)) throw new HttpApiException("Invalid compress type provided", 400); @@ -156,7 +154,7 @@ public class ServerFileSystemController : Controller await ServerFileSystemService.Decompress(server, type, request.Path, request.Destination); } - private async Task GetServerById(int serverId) + private async Task GetServerById(int serverId, ServerPermissionType type) { var server = await ServerRepository .Get() @@ -166,11 +164,7 @@ public class ServerFileSystemController : Controller if (server == null) throw new HttpApiException("No server with this id found", 404); - var userIdClaim = User.Claims.First(x => x.Type == "userId"); - var userId = int.Parse(userIdClaim.Value); - var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); - - if (!ServerService.IsAllowedToAccess(user, server)) + if (!await AuthorizeService.Authorize(User, server, permission => permission.Name == "files" && permission.Type >= type)) throw new HttpApiException("No server with this id found", 404); return server; diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerPowerController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/PowerController.cs similarity index 76% rename from MoonlightServers.ApiServer/Http/Controllers/Client/ServerPowerController.cs rename to MoonlightServers.ApiServer/Http/Controllers/Client/PowerController.cs index c17bbf0..3f6364d 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerPowerController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/PowerController.cs @@ -7,27 +7,31 @@ using MoonCore.Helpers; using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Services; +using MoonlightServers.Shared.Enums; namespace MoonlightServers.ApiServer.Http.Controllers.Client; [ApiController] [Authorize] [Route("api/client/servers")] -public class ServerPowerController : Controller +public class PowerController : Controller { private readonly DatabaseRepository ServerRepository; private readonly DatabaseRepository UserRepository; private readonly ServerService ServerService; + private readonly ServerAuthorizeService AuthorizeService; - public ServerPowerController( + public PowerController( DatabaseRepository serverRepository, DatabaseRepository userRepository, - ServerService serverService + ServerService serverService, + ServerAuthorizeService authorizeService ) { ServerRepository = serverRepository; UserRepository = userRepository; ServerService = serverService; + AuthorizeService = authorizeService; } [HttpPost("{serverId:int}/start")] @@ -54,14 +58,6 @@ public class ServerPowerController : Controller await ServerService.Kill(server); } - [HttpPost("{serverId:int}/install")] - [Authorize] - public async Task Install([FromRoute] int serverId) - { - var server = await GetServerById(serverId); - await ServerService.Install(server); - } - private async Task GetServerById(int serverId) { var server = await ServerRepository @@ -72,11 +68,7 @@ public class ServerPowerController : Controller if (server == null) throw new HttpApiException("No server with this id found", 404); - var userIdClaim = User.Claims.First(x => x.Type == "userId"); - var userId = int.Parse(userIdClaim.Value); - var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); - - if (!ServerService.IsAllowedToAccess(user, server)) + if (!await AuthorizeService.Authorize(User, server, permission => permission is { Name: "power", Type: ServerPermissionType.ReadWrite })) throw new HttpApiException("No server with this id found", 404); return server; diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs index 43861a3..e0ba7ae 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs @@ -1,3 +1,4 @@ +using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -7,35 +8,49 @@ using MoonCore.Models; using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Extensions; +using MoonlightServers.ApiServer.Models; using MoonlightServers.ApiServer.Services; -using MoonlightServers.Shared.Http.Responses.User.Allocations; -using MoonlightServers.Shared.Http.Responses.Users.Servers; +using MoonlightServers.Shared.Enums; +using MoonlightServers.Shared.Http.Responses.Client.Servers; +using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations; namespace MoonlightServers.ApiServer.Http.Controllers.Client; +[Authorize] [ApiController] [Route("api/client/servers")] public class ServersController : Controller { private readonly ServerService ServerService; private readonly DatabaseRepository ServerRepository; - private readonly DatabaseRepository UserRepository; + private readonly DatabaseRepository ShareRepository; private readonly NodeService NodeService; + private readonly ServerAuthorizeService AuthorizeService; - public ServersController(DatabaseRepository serverRepository, NodeService nodeService, ServerService serverService, DatabaseRepository userRepository) + public ServersController( + DatabaseRepository serverRepository, + NodeService nodeService, + ServerService serverService, + ServerAuthorizeService authorizeService, + DatabaseRepository shareRepository + ) { ServerRepository = serverRepository; NodeService = nodeService; ServerService = serverService; - UserRepository = userRepository; + AuthorizeService = authorizeService; + ShareRepository = shareRepository; } [HttpGet] - [Authorize] public async Task> GetAll([FromQuery] int page, [FromQuery] int pageSize) { - var userIdClaim = User.Claims.First(x => x.Type == "userId"); - var userId = int.Parse(userIdClaim.Value); + var userIdClaim = User.FindFirstValue("userId"); + + if (string.IsNullOrEmpty(userIdClaim)) + throw new HttpApiException("Only users are able to use this endpoint", 400); + + var userId = int.Parse(userIdClaim); var query = ServerRepository .Get() @@ -53,6 +68,55 @@ public class ServersController : Controller Name = x.Name, NodeName = x.Node.Name, StarName = x.Star.Name, + Cpu = x.Cpu, + Memory = x.Memory, + Disk = x.Disk, + Allocations = x.Allocations.Select(y => new AllocationDetailResponse() + { + Id = y.Id, + Port = y.Port, + IpAddress = y.IpAddress + }).ToArray() + }).ToArray(); + + return new PagedData() + { + Items = mappedItems, + CurrentPage = page, + PageSize = pageSize, + TotalItems = count, + TotalPages = count == 0 ? 0 : count / pageSize + }; + } + + [HttpGet("shared")] + public async Task> GetAllShared([FromQuery] int page, [FromQuery] int pageSize) + { + var userIdClaim = User.FindFirstValue("userId"); + + if (string.IsNullOrEmpty(userIdClaim)) + throw new HttpApiException("Only users are able to use this endpoint", 400); + + var userId = int.Parse(userIdClaim); + + var query = ShareRepository + .Get() + .Include(x => x.Server) + .Where(x => x.UserId == userId) + .Select(x => x.Server); + + var count = await query.CountAsync(); + var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync(); + + var mappedItems = items.Select(x => new ServerDetailResponse() + { + Id = x.Id, + Name = x.Name, + NodeName = x.Node.Name, + StarName = x.Star.Name, + Cpu = x.Cpu, + Memory = x.Memory, + Disk = x.Disk, Allocations = x.Allocations.Select(y => new AllocationDetailResponse() { Id = y.Id, @@ -72,7 +136,6 @@ public class ServersController : Controller } [HttpGet("{serverId:int}")] - [Authorize] public async Task Get([FromRoute] int serverId) { var server = await ServerRepository @@ -81,15 +144,11 @@ public class ServersController : Controller .Include(x => x.Star) .Include(x => x.Node) .FirstOrDefaultAsync(x => x.Id == serverId); - - if(server == null) + + if (server == null) throw new HttpApiException("No server with this id found", 404); - - var userIdClaim = User.Claims.First(x => x.Type == "userId"); - var userId = int.Parse(userIdClaim.Value); - var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); - - if(!ServerService.IsAllowedToAccess(user, server)) + + if (!await AuthorizeService.Authorize(User, server)) throw new HttpApiException("No server with this id found", 404); return new ServerDetailResponse() @@ -98,6 +157,9 @@ public class ServersController : Controller Name = server.Name, NodeName = server.Node.Name, StarName = server.Star.Name, + Cpu = server.Cpu, + Memory = server.Memory, + Disk = server.Disk, Allocations = server.Allocations.Select(y => new AllocationDetailResponse() { Id = y.Id, @@ -108,10 +170,10 @@ public class ServersController : Controller } [HttpGet("{serverId:int}/status")] - [Authorize] public async Task GetStatus([FromRoute] int serverId) { var server = await GetServerById(serverId); + var status = await ServerService.GetStatus(server); return new ServerStatusResponse() @@ -119,12 +181,14 @@ public class ServersController : Controller State = status.State.ToServerPowerState() }; } - + [HttpGet("{serverId:int}/ws")] - [Authorize] public async Task GetWebSocket([FromRoute] int serverId) { - var server = await GetServerById(serverId); + var server = await GetServerById( + serverId, + permission => permission is { Name: "console", Type: >= ServerPermissionType.Read } + ); // TODO: Handle transparent node proxy @@ -133,7 +197,7 @@ public class ServersController : Controller parameters.Add("type", "websocket"); parameters.Add("serverId", server.Id); }, TimeSpan.FromMinutes(15)); // TODO: Configurable - + var url = ""; url += server.Node.UseSsl ? "https://" : "http://"; @@ -145,36 +209,54 @@ public class ServersController : Controller AccessToken = accessToken }; } - + [HttpGet("{serverId:int}/logs")] - [Authorize] public async Task GetLogs([FromRoute] int serverId) { - var server = await GetServerById(serverId); + var server = await GetServerById( + serverId, + permission => permission is { Name: "console", Type: >= ServerPermissionType.Read } + ); var logs = await ServerService.GetLogs(server); - + return new ServerLogsResponse() { Messages = logs.Messages }; } - private async Task GetServerById(int serverId) + [HttpGet("{serverId:int}/stats")] + public async Task GetStats([FromRoute] int serverId) + { + var server = await GetServerById( + serverId + ); + + var stats = await ServerService.GetStats(server); + + return new ServerStatsResponse() + { + CpuUsage = stats.CpuUsage, + MemoryUsage = stats.MemoryUsage, + NetworkRead = stats.NetworkRead, + NetworkWrite = stats.NetworkWrite, + IoRead = stats.IoRead, + IoWrite = stats.IoWrite + }; + } + + private async Task GetServerById(int serverId, Func? filter = null) { var server = await ServerRepository .Get() .Include(x => x.Node) .FirstOrDefaultAsync(x => x.Id == serverId); - - if(server == null) + + if (server == null) throw new HttpApiException("No server with this id found", 404); - - var userIdClaim = User.Claims.First(x => x.Type == "userId"); - var userId = int.Parse(userIdClaim.Value); - var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); - - if(!ServerService.IsAllowedToAccess(user, server)) + + if (!await AuthorizeService.Authorize(User, server, filter)) throw new HttpApiException("No server with this id found", 404); return server; diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs new file mode 100644 index 0000000..ca1e7b8 --- /dev/null +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/SettingsController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MoonCore.Exceptions; +using MoonCore.Extended.Abstractions; +using MoonlightServers.ApiServer.Database.Entities; +using MoonlightServers.ApiServer.Services; +using MoonlightServers.Shared.Enums; + +namespace MoonlightServers.ApiServer.Http.Controllers.Client; + +[Authorize] +[ApiController] +[Route("api/client/servers")] +public class SettingsController : Controller +{ + private readonly ServerService ServerService; + private readonly DatabaseRepository ServerRepository; + private readonly ServerAuthorizeService AuthorizeService; + + public SettingsController( + ServerService serverService, + DatabaseRepository serverRepository, + ServerAuthorizeService authorizeService + ) + { + ServerService = serverService; + ServerRepository = serverRepository; + AuthorizeService = authorizeService; + } + + [HttpPost("{serverId:int}/install")] + [Authorize] + public async Task Install([FromRoute] int serverId) + { + var server = await GetServerById(serverId); + await ServerService.Install(server); + } + + private async Task GetServerById(int serverId) + { + var server = await ServerRepository + .Get() + .Include(x => x.Node) + .FirstOrDefaultAsync(x => x.Id == serverId); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + if (!await AuthorizeService.Authorize(User, server, permission => permission is { Name: "settings", Type: ServerPermissionType.ReadWrite })) + throw new HttpApiException("No server with this id found", 404); + + return server; + } +} \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/SharesController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/SharesController.cs new file mode 100644 index 0000000..f6441c0 --- /dev/null +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/SharesController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace MoonlightServers.ApiServer.Http.Controllers.Client; + +[Authorize] +[ApiController] +[Route("api/client/servers")] +public class SharesController : Controller +{ + +} \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerVariablesController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/VariablesController.cs similarity index 74% rename from MoonlightServers.ApiServer/Http/Controllers/Client/ServerVariablesController.cs rename to MoonlightServers.ApiServer/Http/Controllers/Client/VariablesController.cs index cfd1408..ba30513 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerVariablesController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/VariablesController.cs @@ -6,6 +6,7 @@ using MoonCore.Extended.Abstractions; using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Services; +using MoonlightServers.Shared.Enums; using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables; using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables; @@ -14,32 +15,29 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Client; [Authorize] [ApiController] [Route("api/client/servers")] -public class ServerVariablesController : Controller +public class VariablesController : Controller { private readonly DatabaseRepository ServerRepository; - private readonly DatabaseRepository UserRepository; - private readonly ServerService ServerService; + private readonly ServerAuthorizeService AuthorizeService; - public ServerVariablesController( + public VariablesController( DatabaseRepository serverRepository, - DatabaseRepository userRepository, - ServerService serverService + ServerAuthorizeService authorizeService ) { ServerRepository = serverRepository; - UserRepository = userRepository; - ServerService = serverService; + AuthorizeService = authorizeService; } [HttpGet("{serverId:int}/variables")] public async Task Get([FromRoute] int serverId) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.Read); return server.Star.Variables.Select(starVariable => { var serverVariable = server.Variables.First(x => x.Key == starVariable.Key); - + return new ServerVariableDetailResponse() { Key = starVariable.Key, @@ -53,18 +51,21 @@ public class ServerVariablesController : Controller } [HttpPut("{serverId:int}/variables")] - public async Task UpdateSingle([FromRoute] int serverId, [FromBody] UpdateServerVariableRequest request) + public async Task UpdateSingle( + [FromRoute] int serverId, + [FromBody] UpdateServerVariableRequest request + ) { // TODO: Handle filter - - var server = await GetServerById(serverId); + + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); var serverVariable = server.Variables.FirstOrDefault(x => x.Key == request.Key); var starVariable = server.Star.Variables.FirstOrDefault(x => x.Key == request.Key); - + if (serverVariable == null || starVariable == null) throw new HttpApiException($"No variable with the key found: {request.Key}", 400); - + serverVariable.Value = request.Value; await ServerRepository.Update(server); @@ -80,9 +81,12 @@ public class ServerVariablesController : Controller } [HttpPatch("{serverId:int}/variables")] - public async Task Update([FromRoute] int serverId, [FromBody] UpdateServerVariableRangeRequest request) + public async Task Update( + [FromRoute] int serverId, + [FromBody] UpdateServerVariableRangeRequest request + ) { - var server = await GetServerById(serverId); + var server = await GetServerById(serverId, ServerPermissionType.ReadWrite); foreach (var variable in request.Variables) { @@ -98,20 +102,25 @@ public class ServerVariablesController : Controller } await ServerRepository.Update(server); - + return request.Variables.Select(requestVariable => { var serverVariable = server.Variables.First(x => x.Key == requestVariable.Key); var starVariable = server.Star.Variables.First(x => x.Key == requestVariable.Key); - + return new ServerVariableDetailResponse() { - + Key = starVariable.Key, + Value = serverVariable.Value, + Type = starVariable.Type, + Name = starVariable.Name, + Description = starVariable.Description, + Filter = starVariable.Filter }; }).ToArray(); } - private async Task GetServerById(int serverId) + private async Task GetServerById(int serverId, ServerPermissionType type) { var server = await ServerRepository .Get() @@ -123,11 +132,8 @@ public class ServerVariablesController : Controller if (server == null) throw new HttpApiException("No server with this id found", 404); - var userIdClaim = User.Claims.First(x => x.Type == "userId"); - var userId = int.Parse(userIdClaim.Value); - var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); - - if (!ServerService.IsAllowedToAccess(user, server)) + if (!await AuthorizeService.Authorize(User, server, + permission => permission.Name == "variables" && permission.Type >= type)) throw new HttpApiException("No server with this id found", 404); return server; diff --git a/MoonlightServers.ApiServer/Models/ServerSharePermission.cs b/MoonlightServers.ApiServer/Models/ServerSharePermission.cs new file mode 100644 index 0000000..b4ab911 --- /dev/null +++ b/MoonlightServers.ApiServer/Models/ServerSharePermission.cs @@ -0,0 +1,9 @@ +using MoonlightServers.Shared.Enums; + +namespace MoonlightServers.ApiServer.Models; + +public class ServerSharePermission +{ + public string Name { get; set; } + public ServerPermissionType Type { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs b/MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs new file mode 100644 index 0000000..a233068 --- /dev/null +++ b/MoonlightServers.ApiServer/Services/ServerAuthorizeService.cs @@ -0,0 +1,129 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; +using MoonCore.Attributes; +using MoonCore.Extended.Abstractions; +using MoonlightServers.ApiServer.Database.Entities; +using MoonlightServers.ApiServer.Models; +using MoonlightServers.Shared.Enums; + +namespace MoonlightServers.ApiServer.Services; + +[Scoped] +public class ServerAuthorizeService +{ + private readonly IAuthorizationService AuthorizationService; + private readonly DatabaseRepository ShareRepository; + + public ServerAuthorizeService( + IAuthorizationService authorizationService, + DatabaseRepository shareRepository + ) + { + AuthorizationService = authorizationService; + ShareRepository = shareRepository; + } + + public async Task Authorize(ClaimsPrincipal user, Server server, Func? filter = null) + { + var userIdClaim = user.FindFirst("userId"); + + // User specific authorization + if (userIdClaim != null && await AuthorizeViaUser(userIdClaim, server, filter)) + return true; + + // Permission specific authorization + return await AuthorizeViaPermission(user); + } + + private async Task AuthorizeViaUser(Claim userIdClaim, Server server, Func? filter = null) + { + var userId = int.Parse(userIdClaim.Value); + + if (server.OwnerId == userId) + return true; + + var possibleShare = await ShareRepository + .Get() + .FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.UserId == userId); + + if (possibleShare == null) + return false; + + // If no filter has been specified every server share is valid + // no matter which permission the share actually has + if (filter == null) + return true; + + var permissionsOfShare = ParsePermissions(possibleShare.Permissions); + + return permissionsOfShare.Any(filter); + } + + private async Task AuthorizeViaPermission(ClaimsPrincipal user) + { + var authorizeResult = await AuthorizationService.AuthorizeAsync( + user, + "permissions:admin.servers.get" + ); + + return authorizeResult.Succeeded; + } + + private ServerSharePermission[] ParsePermissions(string permissionsString) + { + var result = new List(); + + var permissions = permissionsString.Split(';', StringSplitOptions.RemoveEmptyEntries); + + foreach (var permission in permissions) + { + var permissionParts = permission.Split(':', StringSplitOptions.RemoveEmptyEntries); + + // Skipped malformed permission parts + if(permissionParts.Length != 2) + continue; + + if(!Enum.TryParse(permissionParts[1], true, out ServerPermissionType permissionType)) + continue; + + result.Add(new() + { + Name = permissionParts[0], + Type = permissionType + }); + } + + return result.ToArray(); + } + + private bool CheckSharePermission(ServerShare share, string permission, ServerPermissionType type) + { + if (string.IsNullOrEmpty(share.Permissions)) + return false; + + var permissions = share.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries); + + foreach (var sharePermission in permissions) + { + if (!sharePermission.StartsWith(permission)) + continue; + + var typeParts = sharePermission.Split(':', StringSplitOptions.RemoveEmptyEntries); + + // Missing permission type + if (typeParts.Length != 2) + return false; + + // Parse type id + if (!int.TryParse(typeParts[1], out var typeId)) + return false; // Malformed + + var requiredId = (int)type; + + return typeId >= requiredId; + } + + return false; + } +} \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Services/ServerService.cs b/MoonlightServers.ApiServer/Services/ServerService.cs index 7a8aeb8..c0cbb80 100644 --- a/MoonlightServers.ApiServer/Services/ServerService.cs +++ b/MoonlightServers.ApiServer/Services/ServerService.cs @@ -130,6 +130,19 @@ public class ServerService } } + public async Task GetStats(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + return await apiClient.GetJson($"api/servers/{server.Id}/stats"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + #region Helpers public bool IsAllowedToAccess(User user, Server server) diff --git a/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs b/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs index f0691f1..cf9fc18 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs @@ -64,4 +64,25 @@ public class ServersController : Controller Messages = messages }; } + + [HttpGet("{serverId:int}/stats")] + public Task GetStats([FromRoute] int serverId) + { + var server = ServerService.Find(serverId); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + var statsSubSystem = server.GetRequiredSubSystem(); + + return Task.FromResult(new() + { + CpuUsage = statsSubSystem.CurrentStats.CpuUsage, + MemoryUsage = statsSubSystem.CurrentStats.MemoryUsage, + NetworkRead = statsSubSystem.CurrentStats.NetworkRead, + NetworkWrite = statsSubSystem.CurrentStats.NetworkWrite, + IoRead = statsSubSystem.CurrentStats.IoRead, + IoWrite = statsSubSystem.CurrentStats.IoWrite + }); + } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/ServerSystem/SubSystems/StatsSubSystem.cs b/MoonlightServers.Daemon/ServerSystem/SubSystems/StatsSubSystem.cs index 8527e6c..8f47454 100644 --- a/MoonlightServers.Daemon/ServerSystem/SubSystems/StatsSubSystem.cs +++ b/MoonlightServers.Daemon/ServerSystem/SubSystems/StatsSubSystem.cs @@ -8,6 +8,8 @@ namespace MoonlightServers.Daemon.ServerSystem.SubSystems; public class StatsSubSystem : ServerSubSystem { + public ServerStats CurrentStats { get; private set; } + private readonly DockerClient DockerClient; private readonly IHubContext HubContext; @@ -20,6 +22,8 @@ public class StatsSubSystem : ServerSubSystem { DockerClient = dockerClient; HubContext = hubContext; + + CurrentStats = new(); } public Task Attach(string containerId) @@ -44,6 +48,9 @@ public class StatsSubSystem : ServerSubSystem { var stats = ConvertToStats(response); + // Update current stats for usage of other components + CurrentStats = stats; + await HubContext.Clients .Group(Configuration.Id.ToString()) .SendAsync("StatsUpdated", stats); @@ -66,6 +73,9 @@ public class StatsSubSystem : ServerSubSystem } } + // Reset current stats + CurrentStats = new(); + Logger.LogDebug("Stopped fetching container stats"); }); @@ -76,20 +86,16 @@ public class StatsSubSystem : ServerSubSystem { var result = new ServerStats(); - // When killed this field will be null so we just return - if (response.CPUStats.CPUUsage == null) - return result; - #region CPU - - if(response.CPUStats is { CPUUsage.PercpuUsage: not null }) // Sometimes some values are just null >:/ + + if(response.CPUStats != null && response.PreCPUStats.CPUUsage != null) // Sometimes some values are just null >:/ { var cpuDelta = (float)response.CPUStats.CPUUsage.TotalUsage - response.PreCPUStats.CPUUsage.TotalUsage; var cpuSystemDelta = (float)response.CPUStats.SystemUsage - response.PreCPUStats.SystemUsage; var cpuCoreCount = (int)response.CPUStats.OnlineCPUs; - if (cpuCoreCount == 0) + if (cpuCoreCount == 0 && response.CPUStats.CPUUsage.PercpuUsage != null) cpuCoreCount = response.CPUStats.CPUUsage.PercpuUsage.Count; var cpuPercent = 0f; @@ -115,10 +121,13 @@ public class StatsSubSystem : ServerSubSystem #region Network - foreach (var network in response.Networks) + if (response.Networks != null) { - result.NetworkRead += network.Value.RxBytes; - result.NetworkWrite += network.Value.TxBytes; + foreach (var network in response.Networks) + { + result.NetworkRead += network.Value.RxBytes; + result.NetworkWrite += network.Value.TxBytes; + } } #endregion diff --git a/MoonlightServers.Daemon/ServerSystem/SubSystems/StorageSubSystem.cs b/MoonlightServers.Daemon/ServerSystem/SubSystems/StorageSubSystem.cs index 4715459..34cabce 100644 --- a/MoonlightServers.Daemon/ServerSystem/SubSystems/StorageSubSystem.cs +++ b/MoonlightServers.Daemon/ServerSystem/SubSystems/StorageSubSystem.cs @@ -92,6 +92,14 @@ public class StorageSubSystem : ServerSubSystem public async Task Reinitialize() { + if (IsInitialized && StateMachine.State != ServerState.Offline) + { + throw new HttpApiException( + "Unable to reinitialize storage sub system while the server is not offline", + 400 + ); + } + IsInitialized = false; await EnsureRuntimeVolumeCreated(); @@ -303,6 +311,22 @@ public class StorageSubSystem : ServerSubSystem if (existingDiskInfo.Exists) { var expectedSize = ByteConverter.FromMegaBytes(Configuration.Disk).Bytes; + + // If the disk size matches, we are done here + if (expectedSize == existingDiskInfo.Length) + { + Logger.LogDebug("Virtual disk size matches expected size"); + return; + } + + // We cant resize while the server is running as this would lead to possible file corruptions + // and crashes of the software the server is running + if (StateMachine.State != ServerState.Offline) + { + Logger.LogDebug("Skipping disk resizing while server is not offline"); + await ConsoleSubSystem.WriteMoonlight("Skipping disk resizing as the server is not offline"); + return; + } if (expectedSize > existingDiskInfo.Length) { diff --git a/MoonlightServers.Daemon/Services/ServerService.cs b/MoonlightServers.Daemon/Services/ServerService.cs index f44ce73..bfdbfee 100644 --- a/MoonlightServers.Daemon/Services/ServerService.cs +++ b/MoonlightServers.Daemon/Services/ServerService.cs @@ -78,12 +78,14 @@ public class ServerService : IHostedLifecycleService Type[] subSystems = [ + // The restore sub system needs to be on top in order for the state machine having the + // correct state when all other sub systems initialize + typeof(RestoreSubSystem), typeof(ProvisionSubSystem), typeof(StorageSubSystem), typeof(DebugSubSystem), typeof(ShutdownSubSystem), typeof(ConsoleSubSystem), - typeof(RestoreSubSystem), typeof(OnlineDetectionService), typeof(InstallationSubSystem), typeof(StatsSubSystem) diff --git a/MoonlightServers.DaemonShared/DaemonSide/Http/Responses/Servers/ServerStatsResponse.cs b/MoonlightServers.DaemonShared/DaemonSide/Http/Responses/Servers/ServerStatsResponse.cs new file mode 100644 index 0000000..67e107e --- /dev/null +++ b/MoonlightServers.DaemonShared/DaemonSide/Http/Responses/Servers/ServerStatsResponse.cs @@ -0,0 +1,11 @@ +namespace MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers; + +public class ServerStatsResponse +{ + public double CpuUsage { get; set; } + public ulong MemoryUsage { get; set; } + public ulong NetworkRead { get; set; } + public ulong NetworkWrite { get; set; } + public ulong IoRead { get; set; } + public ulong IoWrite { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.Frontend/Implementations/DefaultServerTabProvider.cs b/MoonlightServers.Frontend/Implementations/DefaultServerTabProvider.cs index 5f50a1c..f0be9e3 100644 --- a/MoonlightServers.Frontend/Implementations/DefaultServerTabProvider.cs +++ b/MoonlightServers.Frontend/Implementations/DefaultServerTabProvider.cs @@ -1,7 +1,7 @@ using MoonlightServers.Frontend.Interfaces; using MoonlightServers.Frontend.Models; using MoonlightServers.Frontend.UI.Components.Servers.ServerTabs; -using MoonlightServers.Shared.Http.Responses.Users.Servers; +using MoonlightServers.Shared.Http.Responses.Client.Servers; namespace MoonlightServers.Frontend.Implementations; diff --git a/MoonlightServers.Frontend/Interfaces/IServerTabProvider.cs b/MoonlightServers.Frontend/Interfaces/IServerTabProvider.cs index 6ee8f13..09aacd9 100644 --- a/MoonlightServers.Frontend/Interfaces/IServerTabProvider.cs +++ b/MoonlightServers.Frontend/Interfaces/IServerTabProvider.cs @@ -1,5 +1,5 @@ using MoonlightServers.Frontend.Models; -using MoonlightServers.Shared.Http.Responses.Users.Servers; +using MoonlightServers.Shared.Http.Responses.Client.Servers; namespace MoonlightServers.Frontend.Interfaces; diff --git a/MoonlightServers.Frontend/Services/ServerService.cs b/MoonlightServers.Frontend/Services/ServerService.cs index 1e75800..b0ff18b 100644 --- a/MoonlightServers.Frontend/Services/ServerService.cs +++ b/MoonlightServers.Frontend/Services/ServerService.cs @@ -2,8 +2,8 @@ using MoonCore.Attributes; using MoonCore.Helpers; using MoonCore.Models; using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables; +using MoonlightServers.Shared.Http.Responses.Client.Servers; using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables; -using MoonlightServers.Shared.Http.Responses.Users.Servers; namespace MoonlightServers.Frontend.Services; @@ -45,6 +45,13 @@ public class ServerService ); } + public async Task GetStats(int serverId) + { + return await HttpApiClient.GetJson( + $"api/client/servers/{serverId}/stats" + ); + } + public async Task GetWebSocket(int serverId) { return await HttpApiClient.GetJson( diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor index 8104cb2..d482a12 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor @@ -1,6 +1,7 @@ +@using MoonCore.Helpers @using MoonlightServers.Frontend.Services @using MoonlightServers.Shared.Enums -@using MoonlightServers.Shared.Http.Responses.Users.Servers +@using MoonlightServers.Shared.Http.Responses.Client.Servers @inject ServerService ServerService @inject ILogger Logger @@ -57,7 +58,7 @@ -
56,8%
+
@(Stats.CpuUsage)%
@@ -65,7 +66,7 @@
-
4,2 GB / 8 GB
+
@(Formatter.FormatSize(Stats.MemoryUsage)) / @(Formatter.FormatSize(ByteConverter.FromMegaBytes(Server.Memory).Bytes))
@@ -147,6 +148,7 @@ [Parameter] public ServerDetailResponse Server { get; set; } private ServerStatusResponse Status; + private ServerStatsResponse Stats; private bool IsFailed = false; private bool IsLoaded = false; @@ -159,6 +161,7 @@ try { Status = await ServerService.GetStatus(Server.Id); + Stats = await ServerService.GetStats(Server.Id); } catch (Exception e) { diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/BaseServerTab.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/BaseServerTab.razor index eb14e40..c5271e6 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/BaseServerTab.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/BaseServerTab.razor @@ -1,7 +1,7 @@ @using Microsoft.AspNetCore.SignalR.Client @using MoonlightServers.Frontend.UI.Views.Client @using MoonlightServers.Shared.Enums -@using MoonlightServers.Shared.Http.Responses.Users.Servers +@using MoonlightServers.Shared.Http.Responses.Client.Servers @code { diff --git a/MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor b/MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor index f0abeca..d4e7afa 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor @@ -1,4 +1,4 @@ -@using MoonlightServers.Shared.Http.Responses.Users.Servers +@using MoonlightServers.Shared.Http.Responses.Client.Servers @{ var gradient = Status switch { diff --git a/MoonlightServers.Frontend/UI/Views/Client/Index.razor b/MoonlightServers.Frontend/UI/Views/Client/Index.razor index ed15789..1ea741f 100644 --- a/MoonlightServers.Frontend/UI/Views/Client/Index.razor +++ b/MoonlightServers.Frontend/UI/Views/Client/Index.razor @@ -4,13 +4,21 @@ @using MoonCore.Blazor.Tailwind.Components @using MoonCore.Models @using MoonlightServers.Frontend.Services -@using MoonlightServers.Shared.Http.Responses.Users.Servers +@using MoonlightServers.Shared.Http.Responses.Client.Servers @inject ServerService ServerService -
- @* Folder design idea + @if (Servers.Length == 0) + { + + There are no servers linked to your account + + } + else + { +
+ @* Folder design idea
@@ -28,11 +36,12 @@
*@ - @foreach (var server in Servers) - { - - } -
+ @foreach (var server in Servers) + { + + } +
+ } @code diff --git a/MoonlightServers.Frontend/UI/Views/Client/Manage.razor b/MoonlightServers.Frontend/UI/Views/Client/Manage.razor index ada2611..f66c090 100644 --- a/MoonlightServers.Frontend/UI/Views/Client/Manage.razor +++ b/MoonlightServers.Frontend/UI/Views/Client/Manage.razor @@ -3,7 +3,6 @@ @using Microsoft.AspNetCore.Http.Connections @using Microsoft.AspNetCore.SignalR.Client -@using MoonlightServers.Shared.Http.Responses.Users.Servers @using MoonCore.Blazor.Tailwind.Components @using MoonCore.Exceptions @using MoonCore.Helpers @@ -11,6 +10,7 @@ @using MoonlightServers.Frontend.Models @using MoonlightServers.Frontend.Services @using MoonlightServers.Shared.Enums +@using MoonlightServers.Shared.Http.Responses.Client.Servers @inject ServerService ServerService @inject NavigationManager Navigation diff --git a/MoonlightServers.Shared/Enums/ServerPermissionType.cs b/MoonlightServers.Shared/Enums/ServerPermissionType.cs new file mode 100644 index 0000000..a088411 --- /dev/null +++ b/MoonlightServers.Shared/Enums/ServerPermissionType.cs @@ -0,0 +1,7 @@ +namespace MoonlightServers.Shared.Enums; + +public enum ServerPermissionType +{ + Read = 0, + ReadWrite = 1 +} \ No newline at end of file diff --git a/MoonlightServers.Shared/Http/Responses/Users/Allocations/AllocationDetailResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/Allocations/AllocationDetailResponse.cs similarity index 66% rename from MoonlightServers.Shared/Http/Responses/Users/Allocations/AllocationDetailResponse.cs rename to MoonlightServers.Shared/Http/Responses/Client/Servers/Allocations/AllocationDetailResponse.cs index 5d544b4..c280717 100644 --- a/MoonlightServers.Shared/Http/Responses/Users/Allocations/AllocationDetailResponse.cs +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/Allocations/AllocationDetailResponse.cs @@ -1,4 +1,4 @@ -namespace MoonlightServers.Shared.Http.Responses.User.Allocations; +namespace MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations; public class AllocationDetailResponse { diff --git a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerDetailResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerDetailResponse.cs similarity index 51% rename from MoonlightServers.Shared/Http/Responses/Users/Servers/ServerDetailResponse.cs rename to MoonlightServers.Shared/Http/Responses/Client/Servers/ServerDetailResponse.cs index afa69da..d2e6794 100644 --- a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerDetailResponse.cs +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerDetailResponse.cs @@ -1,12 +1,16 @@ -using MoonlightServers.Shared.Http.Responses.User.Allocations; +using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations; -namespace MoonlightServers.Shared.Http.Responses.Users.Servers; +namespace MoonlightServers.Shared.Http.Responses.Client.Servers; public class ServerDetailResponse { public int Id { get; set; } public string Name { get; set; } + + public int Cpu { get; set; } + public int Memory { get; set; } + public int Disk { get; set; } public string NodeName { get; set; } public string StarName { get; set; } diff --git a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerLogsResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerLogsResponse.cs similarity index 54% rename from MoonlightServers.Shared/Http/Responses/Users/Servers/ServerLogsResponse.cs rename to MoonlightServers.Shared/Http/Responses/Client/Servers/ServerLogsResponse.cs index 1d07fc8..da36ac7 100644 --- a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerLogsResponse.cs +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerLogsResponse.cs @@ -1,4 +1,4 @@ -namespace MoonlightServers.Shared.Http.Responses.Users.Servers; +namespace MoonlightServers.Shared.Http.Responses.Client.Servers; public class ServerLogsResponse { diff --git a/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerStatsResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerStatsResponse.cs new file mode 100644 index 0000000..2d159bf --- /dev/null +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerStatsResponse.cs @@ -0,0 +1,11 @@ +namespace MoonlightServers.Shared.Http.Responses.Client.Servers; + +public class ServerStatsResponse +{ + public double CpuUsage { get; set; } + public ulong MemoryUsage { get; set; } + public ulong NetworkRead { get; set; } + public ulong NetworkWrite { get; set; } + public ulong IoRead { get; set; } + public ulong IoWrite { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerStatusResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerStatusResponse.cs similarity index 64% rename from MoonlightServers.Shared/Http/Responses/Users/Servers/ServerStatusResponse.cs rename to MoonlightServers.Shared/Http/Responses/Client/Servers/ServerStatusResponse.cs index 0299bc9..1a256d8 100644 --- a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerStatusResponse.cs +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerStatusResponse.cs @@ -1,6 +1,6 @@ using MoonlightServers.Shared.Enums; -namespace MoonlightServers.Shared.Http.Responses.Users.Servers; +namespace MoonlightServers.Shared.Http.Responses.Client.Servers; public class ServerStatusResponse { diff --git a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerWebSocketResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerWebSocketResponse.cs similarity index 65% rename from MoonlightServers.Shared/Http/Responses/Users/Servers/ServerWebSocketResponse.cs rename to MoonlightServers.Shared/Http/Responses/Client/Servers/ServerWebSocketResponse.cs index 26165cb..ba1d227 100644 --- a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerWebSocketResponse.cs +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/ServerWebSocketResponse.cs @@ -1,4 +1,4 @@ -namespace MoonlightServers.Shared.Http.Responses.Users.Servers; +namespace MoonlightServers.Shared.Http.Responses.Client.Servers; public class ServerWebSocketResponse { diff --git a/MoonlightServers.Shared/Http/Responses/Client/Servers/Shares/ServerShareResponse.cs b/MoonlightServers.Shared/Http/Responses/Client/Servers/Shares/ServerShareResponse.cs new file mode 100644 index 0000000..32ac3e9 --- /dev/null +++ b/MoonlightServers.Shared/Http/Responses/Client/Servers/Shares/ServerShareResponse.cs @@ -0,0 +1,9 @@ +namespace MoonlightServers.Shared.Http.Responses.Client.Servers.Shares; + +public class ServerShareResponse +{ + public int Id { get; set; } + + public string Email { get; set; } + public string Permissions { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.Shared/MoonlightServers.Shared.csproj b/MoonlightServers.Shared/MoonlightServers.Shared.csproj index 818dc21..faecfcf 100644 --- a/MoonlightServers.Shared/MoonlightServers.Shared.csproj +++ b/MoonlightServers.Shared/MoonlightServers.Shared.csproj @@ -13,5 +13,9 @@ shared true + + + +