Implemented importing and exporting of stars
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using MoonlightServers.ApiServer.Services;
|
||||||
|
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/servers/stars")]
|
||||||
|
public class StarImportExportController : Controller
|
||||||
|
{
|
||||||
|
private readonly StarImportExportService ImportExportService;
|
||||||
|
|
||||||
|
public StarImportExportController(StarImportExportService importExportService)
|
||||||
|
{
|
||||||
|
ImportExportService = importExportService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{starId:int}/export")]
|
||||||
|
[RequirePermission("admin.servers.stars.get")]
|
||||||
|
public async Task Export([FromRoute] int starId)
|
||||||
|
{
|
||||||
|
var exportedStar = await ImportExportService.Export(starId);
|
||||||
|
|
||||||
|
Response.StatusCode = 200;
|
||||||
|
Response.ContentType = "application/json";
|
||||||
|
await Response.WriteAsync(exportedStar);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("import")]
|
||||||
|
[RequirePermission("admin.servers.stars.create")]
|
||||||
|
public async Task<StarDetailResponse> Import()
|
||||||
|
{
|
||||||
|
if (Request.Form.Files.Count == 0)
|
||||||
|
throw new HttpApiException("No file to import provided", 400);
|
||||||
|
|
||||||
|
if (Request.Form.Files.Count > 1)
|
||||||
|
throw new HttpApiException("Only one file to import allowed", 400);
|
||||||
|
|
||||||
|
var file = Request.Form.Files[0];
|
||||||
|
await using var stream = file.OpenReadStream();
|
||||||
|
using var sr = new StreamReader(stream, Encoding.UTF8);
|
||||||
|
var content = await sr.ReadToEndAsync();
|
||||||
|
|
||||||
|
var star = await ImportExportService.Import(content);
|
||||||
|
|
||||||
|
return Mapper.Map<StarDetailResponse>(star);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
MoonlightServers.ApiServer/Models/Stars/StarExportModel.cs
Normal file
54
MoonlightServers.ApiServer/Models/Stars/StarExportModel.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using MoonlightServers.Shared.Enums;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Models.Stars;
|
||||||
|
|
||||||
|
public class StarExportModel
|
||||||
|
{
|
||||||
|
// Meta
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Author { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
public string? DonateUrl { get; set; }
|
||||||
|
public string? UpdateUrl { get; set; }
|
||||||
|
|
||||||
|
// Start and stop
|
||||||
|
public string StartupCommand { get; set; }
|
||||||
|
public string StopCommand { get; set; }
|
||||||
|
public string OnlineDetection { get; set; }
|
||||||
|
|
||||||
|
// Install
|
||||||
|
public string InstallShell { get; set; }
|
||||||
|
public string InstallDockerImage { get; set; }
|
||||||
|
public string InstallScript { get; set; }
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
public int RequiredAllocations { get; set; }
|
||||||
|
public bool AllowDockerImageChange { get; set; }
|
||||||
|
public string ParseConfiguration { get; set; }
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
public StarVariableExportModel[] Variables { get; set; }
|
||||||
|
public StarDockerImageExportModel[] DockerImages { get; set; }
|
||||||
|
|
||||||
|
public class StarDockerImageExportModel
|
||||||
|
{
|
||||||
|
public string DisplayName { get; set; }
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public bool AutoPulling { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StarVariableExportModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public string Key { get; set; }
|
||||||
|
public string DefaultValue { get; set; }
|
||||||
|
|
||||||
|
public bool AllowViewing { get; set; }
|
||||||
|
public bool AllowEditing { get; set; }
|
||||||
|
|
||||||
|
public StarVariableType Type { get; set; }
|
||||||
|
public string? Filter { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,6 @@
|
|||||||
<Folder Include="Http\Middleware\"/>
|
<Folder Include="Http\Middleware\"/>
|
||||||
<Folder Include="Implementations\"/>
|
<Folder Include="Implementations\"/>
|
||||||
<Folder Include="Interfaces\"/>
|
<Folder Include="Interfaces\"/>
|
||||||
<Folder Include="Models\"/>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using MoonCore.Attributes;
|
|
||||||
|
|
||||||
namespace MoonlightServers.ApiServer.Services;
|
|
||||||
|
|
||||||
[Singleton]
|
|
||||||
public class ExampleService
|
|
||||||
{
|
|
||||||
private readonly Random Random;
|
|
||||||
private readonly ILogger<ExampleService> Logger;
|
|
||||||
|
|
||||||
public ExampleService(ILogger<ExampleService> logger)
|
|
||||||
{
|
|
||||||
Logger = logger;
|
|
||||||
Random = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetValue()
|
|
||||||
{
|
|
||||||
Logger.LogInformation("Generating value");
|
|
||||||
return Random.Next(0, 10324);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
156
MoonlightServers.ApiServer/Services/StarImportExportService.cs
Normal file
156
MoonlightServers.ApiServer/Services/StarImportExportService.cs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
|
using MoonlightServers.ApiServer.Models.Stars;
|
||||||
|
|
||||||
|
namespace MoonlightServers.ApiServer.Services;
|
||||||
|
|
||||||
|
[Scoped]
|
||||||
|
public class StarImportExportService
|
||||||
|
{
|
||||||
|
private readonly DatabaseRepository<Star> StarRepository;
|
||||||
|
private readonly ILogger<StarImportExportService> Logger;
|
||||||
|
|
||||||
|
public StarImportExportService(DatabaseRepository<Star> starRepository, ILogger<StarImportExportService> logger)
|
||||||
|
{
|
||||||
|
StarRepository = starRepository;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> Export(int id)
|
||||||
|
{
|
||||||
|
var star = StarRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.DockerImages)
|
||||||
|
.Include(x => x.Variables)
|
||||||
|
.FirstOrDefault(x => x.Id == id);
|
||||||
|
|
||||||
|
if (star == null)
|
||||||
|
throw new HttpApiException("No star with this id found", 404);
|
||||||
|
|
||||||
|
var exportModel = new StarExportModel()
|
||||||
|
{
|
||||||
|
Name = star.Name,
|
||||||
|
Author = star.Author,
|
||||||
|
Version = star.Version,
|
||||||
|
DonateUrl = star.DonateUrl,
|
||||||
|
UpdateUrl = star.UpdateUrl,
|
||||||
|
InstallScript = star.InstallScript,
|
||||||
|
InstallShell = star.InstallShell,
|
||||||
|
InstallDockerImage = star.InstallDockerImage,
|
||||||
|
OnlineDetection = star.OnlineDetection,
|
||||||
|
StopCommand = star.StopCommand,
|
||||||
|
StartupCommand = star.StartupCommand,
|
||||||
|
ParseConfiguration = star.ParseConfiguration,
|
||||||
|
RequiredAllocations = star.RequiredAllocations,
|
||||||
|
AllowDockerImageChange = star.AllowDockerImageChange,
|
||||||
|
Variables = star.Variables.Select(x => new StarExportModel.StarVariableExportModel()
|
||||||
|
{
|
||||||
|
Name = x.Name,
|
||||||
|
Type = x.Type,
|
||||||
|
Description = x.Description,
|
||||||
|
Filter = x.Filter,
|
||||||
|
Key = x.Key,
|
||||||
|
AllowEditing = x.AllowEditing,
|
||||||
|
AllowViewing = x.AllowViewing,
|
||||||
|
DefaultValue = x.DefaultValue
|
||||||
|
}).ToArray(),
|
||||||
|
DockerImages = star.DockerImages.Select(x => new StarExportModel.StarDockerImageExportModel()
|
||||||
|
{
|
||||||
|
Identifier = x.Identifier,
|
||||||
|
AutoPulling = x.AutoPulling,
|
||||||
|
DisplayName = x.DisplayName
|
||||||
|
}).ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(exportModel, new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
});
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Star> Import(string json)
|
||||||
|
{
|
||||||
|
// Determine which importer to use based on simple patterns
|
||||||
|
if (json.Contains("RequiredAllocations"))
|
||||||
|
return await ImportStar(json);
|
||||||
|
else if (json.Contains("AllocationsNeeded"))
|
||||||
|
return await ImportImage(json);
|
||||||
|
else if (json.Contains("_comment"))
|
||||||
|
return await ImportEgg(json);
|
||||||
|
else
|
||||||
|
throw new HttpApiException("Unable to determine the format of the imported star/image/egg", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Star> ImportStar(string json)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var model = JsonSerializer.Deserialize<StarExportModel>(json, new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(model);
|
||||||
|
|
||||||
|
var star = new Star()
|
||||||
|
{
|
||||||
|
Name = model.Name,
|
||||||
|
Author = model.Author,
|
||||||
|
Version = model.Version,
|
||||||
|
DonateUrl = model.DonateUrl,
|
||||||
|
UpdateUrl = model.UpdateUrl,
|
||||||
|
InstallScript = model.InstallScript,
|
||||||
|
InstallShell = model.InstallShell,
|
||||||
|
InstallDockerImage = model.InstallDockerImage,
|
||||||
|
OnlineDetection = model.OnlineDetection,
|
||||||
|
StopCommand = model.StopCommand,
|
||||||
|
StartupCommand = model.StartupCommand,
|
||||||
|
ParseConfiguration = model.ParseConfiguration,
|
||||||
|
RequiredAllocations = model.RequiredAllocations,
|
||||||
|
AllowDockerImageChange = model.AllowDockerImageChange,
|
||||||
|
Variables = model.Variables.Select(x => new StarVariable()
|
||||||
|
{
|
||||||
|
DefaultValue = x.DefaultValue,
|
||||||
|
Description = x.Description,
|
||||||
|
Filter = x.Filter,
|
||||||
|
Key = x.Key,
|
||||||
|
AllowEditing = x.AllowEditing,
|
||||||
|
AllowViewing = x.AllowViewing,
|
||||||
|
Type = x.Type,
|
||||||
|
Name = x.Name
|
||||||
|
}).ToList(),
|
||||||
|
DockerImages = model.DockerImages.Select(x => new StarDockerImage()
|
||||||
|
{
|
||||||
|
DisplayName = x.DisplayName,
|
||||||
|
AutoPulling = x.AutoPulling,
|
||||||
|
Identifier = x.Identifier
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
var finalStar = StarRepository.Add(star);
|
||||||
|
|
||||||
|
return finalStar;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError("An unhandled error occured while importing star: {e}", e);
|
||||||
|
throw new HttpApiException("An unhandled error occured while importing the star", 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Star> ImportImage(string json)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Star> ImportEgg(string json)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,20 +6,32 @@
|
|||||||
@using MoonCore.Blazor.Tailwind.DataTable
|
@using MoonCore.Blazor.Tailwind.DataTable
|
||||||
@using MoonlightServers.Shared.Http.Responses.Admin.Stars
|
@using MoonlightServers.Shared.Http.Responses.Admin.Stars
|
||||||
@using MoonCore.Blazor.Tailwind.Components
|
@using MoonCore.Blazor.Tailwind.Components
|
||||||
|
@using MoonCore.Blazor.Tailwind.Toasts
|
||||||
|
@using MoonCore.Exceptions
|
||||||
|
@using Moonlight.Client.Services
|
||||||
|
|
||||||
@inject HttpApiClient ApiClient
|
@inject HttpApiClient ApiClient
|
||||||
|
@inject DownloadService DownloadService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<NavTabs Index="3" Names="@UiConstants.AdminNavNames" Links="@UiConstants.AdminNavLinks"/>
|
<NavTabs Index="3" Names="@UiConstants.AdminNavNames" Links="@UiConstants.AdminNavLinks"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MinimalCrud TItem="StarDetailResponse" OnConfigure="OnConfigure">
|
<MinimalCrud @ref="Crud" TItem="StarDetailResponse" OnConfigure="OnConfigure">
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Id)" Title="Id" IsSortable="true"/>
|
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Id)" Title="Id" IsSortable="true"/>
|
||||||
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Name)" Title="Name" IsSortable="true"/>
|
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Name)" Title="Name" IsSortable="true"/>
|
||||||
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Version)" Title="Version" IsSortable="true"/>
|
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Version)" Title="Version" IsSortable="true"/>
|
||||||
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Author)" Title="Author"/>
|
<DataColumn TItem="StarDetailResponse" Field="@(x => x.Author)" Title="Author"/>
|
||||||
</ChildContent>
|
</ChildContent>
|
||||||
|
<Actions>
|
||||||
|
<InputFile id="import-file" hidden="" multiple OnChange="OnImportFiles"/>
|
||||||
|
<label for="import-file" class="btn btn-tertiary cursor-pointer">
|
||||||
|
<i class="icon-file-up mr-2"></i>
|
||||||
|
Import
|
||||||
|
</label>
|
||||||
|
</Actions>
|
||||||
<ItemActions>
|
<ItemActions>
|
||||||
@if (!string.IsNullOrEmpty(context.DonateUrl))
|
@if (!string.IsNullOrEmpty(context.DonateUrl))
|
||||||
{
|
{
|
||||||
@@ -36,11 +48,18 @@
|
|||||||
<span class="align-middle">Update</span>
|
<span class="align-middle">Update</span>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<a href="#" @onclick="() => Export(context)" @onclick:preventDefault class="text-success-500 mr-3">
|
||||||
|
<i class="icon-download align-middle"></i>
|
||||||
|
<span class="align-middle">Export</span>
|
||||||
|
</a>
|
||||||
</ItemActions>
|
</ItemActions>
|
||||||
</MinimalCrud>
|
</MinimalCrud>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
private MinimalCrud<StarDetailResponse> Crud;
|
||||||
|
|
||||||
private void OnConfigure(MinimalCrudOptions<StarDetailResponse> options)
|
private void OnConfigure(MinimalCrudOptions<StarDetailResponse> options)
|
||||||
{
|
{
|
||||||
options.Title = "Stars";
|
options.Title = "Stars";
|
||||||
@@ -51,4 +70,51 @@
|
|||||||
options.UpdateUrl = item => ComponentHelper.GetRouteOfComponent<Update>(item.Id)!;
|
options.UpdateUrl = item => ComponentHelper.GetRouteOfComponent<Update>(item.Id)!;
|
||||||
options.DeleteFunction = async item => await ApiClient.Delete($"api/admin/servers/stars/{item.Id}");
|
options.DeleteFunction = async item => await ApiClient.Delete($"api/admin/servers/stars/{item.Id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Export(StarDetailResponse star)
|
||||||
|
{
|
||||||
|
var json = await ApiClient.GetString($"api/admin/servers/stars/{star.Id}/export");
|
||||||
|
|
||||||
|
var formattedFileName = star.Name.Replace(" ", "_") + ".json";
|
||||||
|
|
||||||
|
await DownloadService.DownloadString(formattedFileName, json);
|
||||||
|
await ToastService.Success($"Successfully exported '{star.Name}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnImportFiles(InputFileChangeEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
IBrowserFile[] files;
|
||||||
|
|
||||||
|
if(eventArgs.FileCount == 0)
|
||||||
|
return;
|
||||||
|
else if (eventArgs.FileCount > 1)
|
||||||
|
files = eventArgs.GetMultipleFiles().ToArray();
|
||||||
|
else
|
||||||
|
files = [eventArgs.File];
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!file.Name.EndsWith(".json"))
|
||||||
|
{
|
||||||
|
await ToastService.Danger($"Failed to import '{file.Name}': Only json files are supported");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var stream = file.OpenReadStream();
|
||||||
|
var content = new MultipartFormDataContent();
|
||||||
|
content.Add(new StreamContent(stream), "file", file.Name);
|
||||||
|
|
||||||
|
var star = await ApiClient.PostJson<StarDetailResponse>("api/admin/servers/stars/import", content);
|
||||||
|
|
||||||
|
await ToastService.Success($"Successfully imported '{star.Name}'");
|
||||||
|
await Crud.Refresh(isSilent: false, bypassCache: true);
|
||||||
|
}
|
||||||
|
catch (HttpApiException e)
|
||||||
|
{
|
||||||
|
await ToastService.Danger($"Failed to import '{file.Name}': {e.Title}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user