Implemented template crud, db entities, import/export, ptero and pelican import
This commit is contained in:
173
MoonlightServers.Api/Admin/Templates/CrudController.cs
Normal file
173
MoonlightServers.Api/Admin/Templates/CrudController.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Shared.Http.Requests;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates")]
|
||||
public class CrudController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Template> DatabaseRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public CrudController(
|
||||
DatabaseRepository<Template> databaseRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
DatabaseRepository = databaseRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<PagedData<TemplateDto>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length,
|
||||
[FromQuery] FilterOptions? filterOptions
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = DatabaseRepository
|
||||
.Query();
|
||||
|
||||
// Filters
|
||||
if (filterOptions != null)
|
||||
{
|
||||
foreach (var filterOption in filterOptions.Filters)
|
||||
{
|
||||
query = filterOption.Key switch
|
||||
{
|
||||
nameof(Template.Name) =>
|
||||
query.Where(role => EF.Functions.ILike(role.Name, $"%{filterOption.Value}%")),
|
||||
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<TemplateDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<DetailedTemplateDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var template = await DatabaseRepository
|
||||
.Query()
|
||||
.Include(x => x.DefaultDockerImage)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with this id found", statusCode: 404);
|
||||
|
||||
return TemplateMapper.ToDetailedDto(template);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Templates.Create)]
|
||||
public async Task<ActionResult<TemplateDto>> CreateAsync([FromBody] CreateTemplateDto request)
|
||||
{
|
||||
var template = TemplateMapper.ToEntity(request);
|
||||
|
||||
// Fill in default values
|
||||
template.LifecycleConfig = new()
|
||||
{
|
||||
StartupCommands = [
|
||||
new StartupCommand
|
||||
{
|
||||
DisplayName = "Default Startup",
|
||||
Command = "bash startup.sh"
|
||||
}
|
||||
],
|
||||
StopCommand = "^C",
|
||||
OnlineLogPatterns = ["I am online"]
|
||||
};
|
||||
|
||||
template.InstallationConfig = new()
|
||||
{
|
||||
DockerImage = "debian",
|
||||
Script = "#!/bin/bash\necho Installing",
|
||||
Shell = "/bin/bash"
|
||||
};
|
||||
|
||||
template.FilesConfig = new()
|
||||
{
|
||||
ConfigurationFiles = []
|
||||
};
|
||||
|
||||
template.MiscellaneousConfig = new()
|
||||
{
|
||||
UseLegacyStartup = true
|
||||
};
|
||||
|
||||
var finalRole = await DatabaseRepository.AddAsync(template);
|
||||
|
||||
return TemplateMapper.ToDto(finalRole);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Edit)]
|
||||
public async Task<ActionResult<DetailedTemplateDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateTemplateDto request)
|
||||
{
|
||||
var template = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with this id found", statusCode: 404);
|
||||
|
||||
TemplateMapper.Merge(template, request);
|
||||
|
||||
template.DefaultDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.Where(x => x.Template.Id == id)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.DefaultDockerImageId);
|
||||
|
||||
await DatabaseRepository.UpdateAsync(template);
|
||||
|
||||
return TemplateMapper.ToDetailedDto(template);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var template = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with this id found", statusCode: 404);
|
||||
|
||||
await DatabaseRepository.RemoveAsync(template);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
144
MoonlightServers.Api/Admin/Templates/DockerImagesController.cs
Normal file
144
MoonlightServers.Api/Admin/Templates/DockerImagesController.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates/{templateId:int}/dockerImages")]
|
||||
public class DockerImagesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
|
||||
public DockerImagesController(
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository,
|
||||
DatabaseRepository<Template> templateRepository
|
||||
)
|
||||
{
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
TemplateRepository = templateRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<PagedData<DockerImageDto>>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
if (!await TemplateRepository.Query().AnyAsync(x => x.Id == templateId))
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
// Query building
|
||||
|
||||
var query = DockerImageRepository
|
||||
.Query()
|
||||
.Where(x => x.Template.Id == templateId);
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<DockerImageDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<DockerImageDto>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateDockerImage == null)
|
||||
return Problem("No template or template dockerImage found with that id");
|
||||
|
||||
return TemplateMapper.ToDto(templateDockerImage);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Templates.Create)]
|
||||
public async Task<ActionResult<DockerImageDto>> CreateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromBody] CreateDockerImageDto dto
|
||||
)
|
||||
{
|
||||
var template = await TemplateRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == templateId);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
var dockerImage = TemplateMapper.ToEntity(dto);
|
||||
|
||||
dockerImage.Template = template;
|
||||
|
||||
var finalDockerImage = await DockerImageRepository.AddAsync(dockerImage);
|
||||
|
||||
return TemplateMapper.ToDto(finalDockerImage);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Edit)]
|
||||
public async Task<ActionResult<DockerImageDto>> UpdateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateDockerImageDto dto
|
||||
)
|
||||
{
|
||||
var templateDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateDockerImage == null)
|
||||
return Problem("No template or template dockerImage found with that id");
|
||||
|
||||
TemplateMapper.Merge(templateDockerImage, dto);
|
||||
|
||||
await DockerImageRepository.UpdateAsync(templateDockerImage);
|
||||
|
||||
return TemplateMapper.ToDto(templateDockerImage);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateDockerImage == null)
|
||||
return Problem("No template or template dockerImage found with that id");
|
||||
|
||||
await DockerImageRepository.RemoveAsync(templateDockerImage);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
207
MoonlightServers.Api/Admin/Templates/PelicanEggImportService.cs
Normal file
207
MoonlightServers.Api/Admin/Templates/PelicanEggImportService.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System.Text;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
using VYaml.Annotations;
|
||||
using VYaml.Serialization;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
public class PelicanEggImportService
|
||||
{
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public PelicanEggImportService(
|
||||
DatabaseRepository<Template> templateRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
TemplateRepository = templateRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
public async Task<Template> ImportAsync(string content)
|
||||
{
|
||||
var egg = YamlSerializer.Deserialize<Egg>(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
var template = new Template()
|
||||
{
|
||||
AllowUserDockerImageChange = true,
|
||||
Author = egg.Author,
|
||||
Description = egg.Description,
|
||||
DonateUrl = null,
|
||||
Name = egg.Name,
|
||||
UpdateUrl = egg.Meta.UpdateUrl,
|
||||
Version = "1.0.0",
|
||||
FilesConfig = new FilesConfig()
|
||||
{
|
||||
ConfigurationFiles = egg.Config.Files.Select(file => new ConfigurationFile()
|
||||
{
|
||||
Path = file.Key,
|
||||
Parser = file.Value.Parser,
|
||||
Mappings = file.Value.Find.Select(pair => new ConfigurationFileMapping()
|
||||
{
|
||||
Key = pair.Key,
|
||||
Value = pair.Value
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
},
|
||||
InstallationConfig = new InstallationConfig()
|
||||
{
|
||||
DockerImage = egg.Scripts.Installation.Container,
|
||||
Script = egg.Scripts.Installation.Script,
|
||||
Shell = egg.Scripts.Installation.Entrypoint
|
||||
},
|
||||
LifecycleConfig = new LifecycleConfig()
|
||||
{
|
||||
OnlineLogPatterns = egg.Config.Startup.Values.ToList(),
|
||||
StopCommand = egg.Config.Stop,
|
||||
StartupCommands = egg.StartupCommands.Select(x => new StartupCommand()
|
||||
{
|
||||
DisplayName = x.Key,
|
||||
Command = x.Value
|
||||
}).ToList()
|
||||
},
|
||||
MiscellaneousConfig = new MiscellaneousConfig()
|
||||
{
|
||||
UseLegacyStartup = true
|
||||
},
|
||||
Variables = egg.Variables.Select(variable => new TemplateVariable()
|
||||
{
|
||||
Description = variable.Description,
|
||||
DisplayName = variable.Name,
|
||||
DefaultValue = variable.DefaultValue,
|
||||
EnvName = variable.EnvVariable
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
var finalTemplate = await TemplateRepository.AddAsync(template);
|
||||
|
||||
var isFirst = true;
|
||||
TemplateDockerImage? defaultDockerImage = null;
|
||||
|
||||
foreach (var dockerImage in egg.DockerImages)
|
||||
{
|
||||
var finalDockerImage = await DockerImageRepository.AddAsync(new TemplateDockerImage()
|
||||
{
|
||||
DisplayName = dockerImage.Key,
|
||||
ImageName = dockerImage.Value,
|
||||
SkipPulling = false,
|
||||
Template = finalTemplate
|
||||
});
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
defaultDockerImage = finalDockerImage;
|
||||
}
|
||||
}
|
||||
|
||||
finalTemplate.DefaultDockerImage = defaultDockerImage;
|
||||
|
||||
await TemplateRepository.UpdateAsync(finalTemplate);
|
||||
|
||||
return finalTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class Egg
|
||||
{
|
||||
[YamlMember("_comment")] public string? Comment { get; set; }
|
||||
|
||||
[YamlMember("meta")] public EggMeta Meta { get; set; } = new();
|
||||
|
||||
[YamlMember("exported_at")] public string? ExportedAt { get; set; }
|
||||
|
||||
[YamlMember("name")] public string Name { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("author")] public string Author { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("uuid")] public string Uuid { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("description")] public string Description { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("image")] public string? Image { get; set; }
|
||||
|
||||
[YamlMember("tags")] public List<string> Tags { get; set; } = new();
|
||||
|
||||
[YamlMember("features")] public List<string> Features { get; set; } = new();
|
||||
|
||||
[YamlMember("docker_images")] public Dictionary<string, string> DockerImages { get; set; } = new();
|
||||
|
||||
[YamlMember("file_denylist")] public Dictionary<string, string> FileDenylist { get; set; } = new();
|
||||
|
||||
[YamlMember("startup_commands")] public Dictionary<string, string> StartupCommands { get; set; } = new();
|
||||
|
||||
[YamlMember("config")] public EggConfig Config { get; set; } = new();
|
||||
|
||||
[YamlMember("scripts")] public EggScripts Scripts { get; set; } = new();
|
||||
|
||||
[YamlMember("variables")] public List<EggVariable> Variables { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggMeta
|
||||
{
|
||||
[YamlMember("version")] public string Version { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("update_url")] public string? UpdateUrl { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggConfig
|
||||
{
|
||||
[YamlMember("files")] public Dictionary<string, EggConfigFile> Files { get; set; } = new();
|
||||
|
||||
[YamlMember("startup")] public Dictionary<string, string> Startup { get; set; } = new();
|
||||
|
||||
[YamlMember("logs")] public Dictionary<string, string> Logs { get; set; } = new();
|
||||
|
||||
[YamlMember("stop")] public string Stop { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggConfigFile
|
||||
{
|
||||
[YamlMember("parser")] public string Parser { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("find")] public Dictionary<string, string> Find { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggScripts
|
||||
{
|
||||
[YamlMember("installation")] public EggInstallationScript Installation { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggInstallationScript
|
||||
{
|
||||
[YamlMember("script")] public string Script { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("container")] public string Container { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("entrypoint")] public string Entrypoint { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggVariable
|
||||
{
|
||||
[YamlMember("name")] public string Name { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("description")] public string Description { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("env_variable")] public string EnvVariable { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("default_value")] public string DefaultValue { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("user_viewable")] public bool UserViewable { get; set; }
|
||||
|
||||
[YamlMember("user_editable")] public bool UserEditable { get; set; }
|
||||
|
||||
[YamlMember("rules")] public List<string> Rules { get; set; } = new();
|
||||
|
||||
[YamlMember("sort")] public int Sort { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
using System.Text.Json;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
public class PterodactylEggImportService
|
||||
{
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public PterodactylEggImportService(
|
||||
DatabaseRepository<Template> templateRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
TemplateRepository = templateRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
public async Task<Template> ImportAsync(string json)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
var template = new Template
|
||||
{
|
||||
Name = Truncate(root.GetStringOrDefault("name") ?? "Unknown", 30),
|
||||
Description = Truncate(root.GetStringOrDefault("description") ?? "", 255),
|
||||
Author = Truncate(root.GetStringOrDefault("author") ?? "", 30),
|
||||
Version = "1.0.0",
|
||||
UpdateUrl = root.TryGetProperty("meta", out var meta)
|
||||
? meta.GetStringOrDefault("update_url")
|
||||
: null,
|
||||
DonateUrl = null,
|
||||
|
||||
FilesConfig = ParseFilesConfig(root),
|
||||
LifecycleConfig = ParseLifecycleConfig(root),
|
||||
InstallationConfig = ParseInstallationConfig(root),
|
||||
MiscellaneousConfig = new MiscellaneousConfig { UseLegacyStartup = true },
|
||||
|
||||
AllowUserDockerImageChange = true,
|
||||
Variables = ParseVariables(root)
|
||||
};
|
||||
|
||||
var finalTemplate = await TemplateRepository.AddAsync(template);
|
||||
|
||||
var dockerImages = ParseDockerImageModels(root);
|
||||
TemplateDockerImage? defaultDockerImage = null;
|
||||
|
||||
var isFirst = true;
|
||||
|
||||
foreach (var (displayName, imageName) in dockerImages)
|
||||
{
|
||||
var entity = new TemplateDockerImage
|
||||
{
|
||||
DisplayName = displayName,
|
||||
ImageName = imageName,
|
||||
SkipPulling = false,
|
||||
Template = finalTemplate
|
||||
};
|
||||
|
||||
var finalEntity = await DockerImageRepository.AddAsync(entity);
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
defaultDockerImage = finalEntity;
|
||||
}
|
||||
}
|
||||
|
||||
finalTemplate.DefaultDockerImage = defaultDockerImage;
|
||||
await TemplateRepository.UpdateAsync(finalTemplate);
|
||||
|
||||
return finalTemplate;
|
||||
}
|
||||
|
||||
private static FilesConfig ParseFilesConfig(JsonElement root)
|
||||
{
|
||||
var configFiles = new List<ConfigurationFile>();
|
||||
|
||||
if (!root.TryGetProperty("config", out var config))
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
|
||||
if (!config.TryGetProperty("files", out var filesElement))
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
|
||||
var filesJson = filesElement.ValueKind == JsonValueKind.String
|
||||
? filesElement.GetString()
|
||||
: filesElement.GetRawText();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filesJson) || filesJson == "{}" || filesJson == "[]")
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
|
||||
try
|
||||
{
|
||||
using var filesDoc = JsonDocument.Parse(filesJson);
|
||||
|
||||
foreach (var fileProperty in filesDoc.RootElement.EnumerateObject())
|
||||
{
|
||||
var parser = fileProperty.Value.GetStringOrDefault("parser") ?? "json";
|
||||
var mappings = new List<ConfigurationFileMapping>();
|
||||
|
||||
if (fileProperty.Value.TryGetProperty("find", out var find))
|
||||
{
|
||||
foreach (var mapping in find.EnumerateObject())
|
||||
{
|
||||
mappings.Add(new ConfigurationFileMapping
|
||||
{
|
||||
Key = mapping.Name,
|
||||
Value = mapping.Value.ValueKind == JsonValueKind.String
|
||||
? mapping.Value.GetString() ?? ""
|
||||
: mapping.Value.GetRawText()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
configFiles.Add(new ConfigurationFile
|
||||
{
|
||||
Path = fileProperty.Name,
|
||||
Parser = parser,
|
||||
Mappings = mappings
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
}
|
||||
|
||||
private static LifecycleConfig ParseLifecycleConfig(JsonElement root)
|
||||
{
|
||||
var stopCommand = "";
|
||||
var onlinePatterns = new List<string>();
|
||||
|
||||
if (root.TryGetProperty("config", out var config))
|
||||
{
|
||||
stopCommand = config.GetStringOrDefault("stop") ?? "";
|
||||
|
||||
if (config.TryGetProperty("startup", out var startupElement))
|
||||
{
|
||||
var startupJson = startupElement.ValueKind == JsonValueKind.String
|
||||
? startupElement.GetString()
|
||||
: startupElement.GetRawText();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(startupJson))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var startupDoc = JsonDocument.Parse(startupJson);
|
||||
|
||||
if (startupDoc.RootElement.TryGetProperty("done", out var done))
|
||||
{
|
||||
var doneValue = done.ValueKind == JsonValueKind.String
|
||||
? done.GetString()
|
||||
: done.GetRawText();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(doneValue))
|
||||
onlinePatterns.Add(doneValue);
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LifecycleConfig
|
||||
{
|
||||
StartupCommands =
|
||||
[
|
||||
new StartupCommand
|
||||
{
|
||||
DisplayName = "Startup",
|
||||
Command = root.GetStringOrDefault("startup") ?? ""
|
||||
}
|
||||
],
|
||||
StopCommand = stopCommand,
|
||||
OnlineLogPatterns = onlinePatterns
|
||||
};
|
||||
}
|
||||
|
||||
private static InstallationConfig ParseInstallationConfig(JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("scripts", out var scripts))
|
||||
return new InstallationConfig();
|
||||
|
||||
if (!scripts.TryGetProperty("installation", out var installation))
|
||||
return new InstallationConfig();
|
||||
|
||||
return new InstallationConfig
|
||||
{
|
||||
DockerImage = installation.GetStringOrDefault("container") ?? "",
|
||||
Shell = installation.GetStringOrDefault("entrypoint") ?? "bash",
|
||||
Script = installation.GetStringOrDefault("script") ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
// Returns (DisplayName, ImageName, IsFirst) tuples to avoid a temporary model
|
||||
private static List<(string DisplayName, string ImageName)> ParseDockerImageModels(JsonElement root)
|
||||
{
|
||||
var result = new List<(string, string)>();
|
||||
|
||||
if (!root.TryGetProperty("docker_images", out var dockerImages))
|
||||
return result;
|
||||
|
||||
foreach (var img in dockerImages.EnumerateObject())
|
||||
{
|
||||
result.Add((
|
||||
Truncate(img.Name, 30),
|
||||
Truncate(img.Value.GetString() ?? img.Name, 255)
|
||||
));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<TemplateVariable> ParseVariables(JsonElement root)
|
||||
{
|
||||
var variables = new List<TemplateVariable>();
|
||||
|
||||
if (!root.TryGetProperty("variables", out var vars))
|
||||
return variables;
|
||||
|
||||
foreach (var v in vars.EnumerateArray())
|
||||
{
|
||||
variables.Add(new TemplateVariable
|
||||
{
|
||||
DisplayName = Truncate(v.GetStringOrDefault("name") ?? "Variable", 30),
|
||||
Description = Truncate(v.GetStringOrDefault("description") ?? "", 255),
|
||||
EnvName = Truncate(v.GetStringOrDefault("env_variable") ?? "", 60),
|
||||
DefaultValue = Truncate(v.GetStringOrDefault("default_value") ?? "", 1024)
|
||||
});
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
private static string Truncate(string value, int maxLength) =>
|
||||
value.Length <= maxLength ? value : value[..maxLength];
|
||||
}
|
||||
|
||||
internal static class JsonElementExtensions
|
||||
{
|
||||
public static string? GetStringOrDefault(this JsonElement element, string propertyName)
|
||||
{
|
||||
return element.TryGetProperty(propertyName, out var prop) && prop.ValueKind == JsonValueKind.String
|
||||
? prop.GetString()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
28
MoonlightServers.Api/Admin/Templates/TemplateMapper.cs
Normal file
28
MoonlightServers.Api/Admin/Templates/TemplateMapper.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[Mapper]
|
||||
[SuppressMessage("Mapper", "RMG020:No members are mapped in an object mapping")]
|
||||
[SuppressMessage("Mapper", "RMG012:No members are mapped in an object mapping")]
|
||||
public static partial class TemplateMapper
|
||||
{
|
||||
public static partial TemplateDto ToDto(Template template);
|
||||
public static partial DetailedTemplateDto ToDetailedDto(Template template);
|
||||
public static partial IQueryable<TemplateDto> ProjectToDto(this IQueryable<Template> templates);
|
||||
public static partial Template ToEntity(CreateTemplateDto dto);
|
||||
public static partial void Merge([MappingTarget] Template template, UpdateTemplateDto dto);
|
||||
|
||||
public static partial IQueryable<VariableDto> ProjectToDto(this IQueryable<TemplateVariable> variables);
|
||||
public static partial VariableDto ToDto(TemplateVariable variable);
|
||||
public static partial TemplateVariable ToEntity(CreateVariableDto dto);
|
||||
public static partial void Merge([MappingTarget] TemplateVariable variable, UpdateVariableDto dto);
|
||||
|
||||
public static partial IQueryable<DockerImageDto> ProjectToDto(this IQueryable<TemplateDockerImage> dockerImages);
|
||||
public static partial DockerImageDto ToDto(TemplateDockerImage dockerImage);
|
||||
public static partial TemplateDockerImage ToEntity(CreateDockerImageDto dto);
|
||||
public static partial void Merge([MappingTarget] TemplateDockerImage dockerImage, UpdateDockerImageDto dto);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using VYaml.Annotations;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[YamlObject]
|
||||
public partial class TemplateTransferModel
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public string Author { get; set; } = "";
|
||||
public string Version { get; set; } = "";
|
||||
public string? UpdateUrl { get; set; }
|
||||
public string? DonateUrl { get; set; }
|
||||
|
||||
public FilesConfigTransferModel Files { get; set; } = new();
|
||||
public LifecycleConfigTransferModel Lifecycle { get; set; } = new();
|
||||
public InstallationConfigTransferModel Installation { get; set; } = new();
|
||||
public MiscellaneousConfigTransferModel Miscellaneous { get; set; } = new();
|
||||
|
||||
public bool AllowUserDockerImageChange { get; set; }
|
||||
public List<TemplateDockerImageTransferModel> DockerImages { get; set; } = new();
|
||||
|
||||
public List<TemplateVariableTransferModel> Variables { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class TemplateDockerImageTransferModel
|
||||
{
|
||||
public string DisplayName { get; set; } = "";
|
||||
public string ImageName { get; set; } = "";
|
||||
public bool SkipPulling { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class TemplateVariableTransferModel
|
||||
{
|
||||
public string DisplayName { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public string EnvName { get; set; } = "";
|
||||
public string? DefaultValue { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class FilesConfigTransferModel
|
||||
{
|
||||
public List<ConfigurationFileTransferModel> ConfigurationFiles { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class ConfigurationFileTransferModel
|
||||
{
|
||||
public string Path { get; set; } = "";
|
||||
public string Parser { get; set; } = "";
|
||||
public List<ConfigurationFileMappingTransferModel> Mappings { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class ConfigurationFileMappingTransferModel
|
||||
{
|
||||
public string Key { get; set; } = "";
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class LifecycleConfigTransferModel
|
||||
{
|
||||
public List<StartupCommandTransferModel> StartupCommands { get; set; } = new();
|
||||
public string StopCommand { get; set; } = "";
|
||||
public List<string> OnlineLogPatterns { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class StartupCommandTransferModel
|
||||
{
|
||||
public string DisplayName { get; set; } = "";
|
||||
public string Command { get; set; } = "";
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class InstallationConfigTransferModel
|
||||
{
|
||||
public string DockerImage { get; set; } = "";
|
||||
public string Shell { get; set; } = "";
|
||||
public string Script { get; set; } = "";
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class MiscellaneousConfigTransferModel
|
||||
{
|
||||
public bool UseLegacyStartup { get; set; }
|
||||
}
|
||||
182
MoonlightServers.Api/Admin/Templates/TemplateTransferService.cs
Normal file
182
MoonlightServers.Api/Admin/Templates/TemplateTransferService.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
public class TemplateTransferService
|
||||
{
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public TemplateTransferService(DatabaseRepository<Template> templateRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository)
|
||||
{
|
||||
TemplateRepository = templateRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
public async Task<TemplateTransferModel?> ExportAsync(int id)
|
||||
{
|
||||
var template = await TemplateRepository
|
||||
.Query()
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.DockerImages)
|
||||
.Include(x => x.DefaultDockerImage)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return null;
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = template.Name,
|
||||
Description = template.Description,
|
||||
Author = template.Author,
|
||||
Version = template.Version,
|
||||
UpdateUrl = template.UpdateUrl,
|
||||
DonateUrl = template.DonateUrl,
|
||||
|
||||
Files = new FilesConfigTransferModel
|
||||
{
|
||||
ConfigurationFiles = template.FilesConfig.ConfigurationFiles
|
||||
.Select(cf => new ConfigurationFileTransferModel
|
||||
{
|
||||
Path = cf.Path,
|
||||
Parser = cf.Parser,
|
||||
Mappings = cf.Mappings
|
||||
.Select(m => new ConfigurationFileMappingTransferModel { Key = m.Key, Value = m.Value })
|
||||
.ToList()
|
||||
})
|
||||
.ToList()
|
||||
},
|
||||
|
||||
Lifecycle = new LifecycleConfigTransferModel
|
||||
{
|
||||
StartupCommands = template.LifecycleConfig.StartupCommands
|
||||
.Select(sc => new StartupCommandTransferModel
|
||||
{ DisplayName = sc.DisplayName, Command = sc.Command })
|
||||
.ToList(),
|
||||
StopCommand = template.LifecycleConfig.StopCommand,
|
||||
OnlineLogPatterns = template.LifecycleConfig.OnlineLogPatterns.ToList()
|
||||
},
|
||||
|
||||
Installation = new InstallationConfigTransferModel
|
||||
{
|
||||
DockerImage = template.InstallationConfig.DockerImage,
|
||||
Shell = template.InstallationConfig.Shell,
|
||||
Script = template.InstallationConfig.Script
|
||||
},
|
||||
|
||||
Miscellaneous = new MiscellaneousConfigTransferModel
|
||||
{
|
||||
UseLegacyStartup = template.MiscellaneousConfig.UseLegacyStartup
|
||||
},
|
||||
|
||||
AllowUserDockerImageChange = template.AllowUserDockerImageChange,
|
||||
DockerImages = template.DockerImages
|
||||
.Select(img => new TemplateDockerImageTransferModel
|
||||
{
|
||||
DisplayName = img.DisplayName,
|
||||
ImageName = img.ImageName,
|
||||
SkipPulling = img.SkipPulling,
|
||||
IsDefault = template.DefaultDockerImage != null && img.Id == template.DefaultDockerImage.Id
|
||||
})
|
||||
.ToList(),
|
||||
|
||||
Variables = template.Variables
|
||||
.Select(v => new TemplateVariableTransferModel
|
||||
{
|
||||
DisplayName = v.DisplayName,
|
||||
Description = v.Description,
|
||||
EnvName = v.EnvName,
|
||||
DefaultValue = v.DefaultValue
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<Template> ImportAsync(TemplateTransferModel m)
|
||||
{
|
||||
var template = new Template
|
||||
{
|
||||
Name = m.Name,
|
||||
Description = m.Description,
|
||||
Author = m.Author,
|
||||
Version = m.Version,
|
||||
UpdateUrl = m.UpdateUrl,
|
||||
DonateUrl = m.DonateUrl,
|
||||
|
||||
FilesConfig = new FilesConfig
|
||||
{
|
||||
ConfigurationFiles = m.Files.ConfigurationFiles
|
||||
.Select(cf => new ConfigurationFile
|
||||
{
|
||||
Path = cf.Path,
|
||||
Parser = cf.Parser,
|
||||
Mappings = cf.Mappings
|
||||
.Select(mp => new ConfigurationFileMapping { Key = mp.Key, Value = mp.Value })
|
||||
.ToList()
|
||||
})
|
||||
.ToList()
|
||||
},
|
||||
|
||||
LifecycleConfig = new LifecycleConfig
|
||||
{
|
||||
StartupCommands = m.Lifecycle.StartupCommands
|
||||
.Select(sc => new StartupCommand { DisplayName = sc.DisplayName, Command = sc.Command })
|
||||
.ToList(),
|
||||
StopCommand = m.Lifecycle.StopCommand,
|
||||
OnlineLogPatterns = m.Lifecycle.OnlineLogPatterns.ToList()
|
||||
},
|
||||
|
||||
InstallationConfig = new InstallationConfig
|
||||
{
|
||||
DockerImage = m.Installation.DockerImage,
|
||||
Shell = m.Installation.Shell,
|
||||
Script = m.Installation.Script
|
||||
},
|
||||
|
||||
MiscellaneousConfig = new MiscellaneousConfig { UseLegacyStartup = m.Miscellaneous.UseLegacyStartup },
|
||||
|
||||
AllowUserDockerImageChange = m.AllowUserDockerImageChange,
|
||||
|
||||
Variables = m.Variables
|
||||
.Select(v => new TemplateVariable
|
||||
{
|
||||
DisplayName = v.DisplayName,
|
||||
Description = v.Description,
|
||||
EnvName = v.EnvName,
|
||||
DefaultValue = v.DefaultValue
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
|
||||
var finalTemplate = await TemplateRepository.AddAsync(template);
|
||||
|
||||
TemplateDockerImage? defaultDockerImage = null;
|
||||
|
||||
foreach (var img in m.DockerImages)
|
||||
{
|
||||
var entity = new TemplateDockerImage
|
||||
{
|
||||
DisplayName = img.DisplayName,
|
||||
ImageName = img.ImageName,
|
||||
SkipPulling = img.SkipPulling,
|
||||
Template = template
|
||||
};
|
||||
|
||||
var finalEntity = await DockerImageRepository.AddAsync(entity);
|
||||
|
||||
if (img.IsDefault)
|
||||
defaultDockerImage = finalEntity;
|
||||
}
|
||||
|
||||
finalTemplate.DefaultDockerImage = defaultDockerImage;
|
||||
|
||||
await TemplateRepository.UpdateAsync(finalTemplate);
|
||||
|
||||
return finalTemplate;
|
||||
}
|
||||
}
|
||||
88
MoonlightServers.Api/Admin/Templates/TransferController.cs
Normal file
88
MoonlightServers.Api/Admin/Templates/TransferController.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
using VYaml.Serialization;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates")]
|
||||
public class TransferController : Controller
|
||||
{
|
||||
private readonly TemplateTransferService TransferService;
|
||||
|
||||
public TransferController(TemplateTransferService transferService)
|
||||
{
|
||||
TransferService = transferService;
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/export")]
|
||||
public async Task<ActionResult> ExportAsync([FromRoute] int id)
|
||||
{
|
||||
var transferModel = await TransferService.ExportAsync(id);
|
||||
|
||||
if (transferModel == null)
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
var yml = YamlSerializer.Serialize(transferModel, new YamlSerializerOptions
|
||||
{
|
||||
Resolver = CompositeResolver.Create([
|
||||
GeneratedResolver.Instance,
|
||||
StandardResolver.Instance
|
||||
])
|
||||
});
|
||||
|
||||
return File(yml.ToArray(), "text/yaml", $"{transferModel.Name}.yml");
|
||||
}
|
||||
|
||||
[HttpPost("import")]
|
||||
public async Task<ActionResult<TemplateDto>> ImportAsync()
|
||||
{
|
||||
string content;
|
||||
|
||||
await using (Stream receiveStream = Request.Body)
|
||||
|
||||
using (StreamReader readStream = new StreamReader(receiveStream))
|
||||
content = await readStream.ReadToEndAsync();
|
||||
|
||||
if(content.Contains("version: PLCN_v3"))
|
||||
{
|
||||
var importService = HttpContext.RequestServices.GetRequiredService<PelicanEggImportService>();
|
||||
|
||||
var template = await importService.ImportAsync(content);
|
||||
|
||||
return TemplateMapper.ToDto(template);
|
||||
}
|
||||
if (
|
||||
content.Contains("PTDL_v2", StringComparison.OrdinalIgnoreCase) ||
|
||||
content.Contains("PLCN_v1", StringComparison.OrdinalIgnoreCase) ||
|
||||
content.Contains("PLCN_v2", StringComparison.OrdinalIgnoreCase) ||
|
||||
content.Contains("PLCN_v3", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
{
|
||||
var importService = HttpContext.RequestServices.GetRequiredService<PterodactylEggImportService>();
|
||||
|
||||
var template = await importService.ImportAsync(content);
|
||||
|
||||
return TemplateMapper.ToDto(template);
|
||||
}
|
||||
else
|
||||
{
|
||||
var transferModel = YamlSerializer.Deserialize<TemplateTransferModel>(
|
||||
Encoding.UTF8.GetBytes(content),
|
||||
new YamlSerializerOptions
|
||||
{
|
||||
Resolver = CompositeResolver.Create([
|
||||
GeneratedResolver.Instance,
|
||||
StandardResolver.Instance
|
||||
])
|
||||
}
|
||||
);
|
||||
|
||||
var template = await TransferService.ImportAsync(transferModel);
|
||||
|
||||
return TemplateMapper.ToDto(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
MoonlightServers.Api/Admin/Templates/VariablesController.cs
Normal file
144
MoonlightServers.Api/Admin/Templates/VariablesController.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates/{templateId:int}/variables")]
|
||||
public class VariablesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<TemplateVariable> VariableRepository;
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
|
||||
public VariablesController(
|
||||
DatabaseRepository<TemplateVariable> variableRepository,
|
||||
DatabaseRepository<Template> templateRepository
|
||||
)
|
||||
{
|
||||
VariableRepository = variableRepository;
|
||||
TemplateRepository = templateRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<PagedData<VariableDto>>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
if (!await TemplateRepository.Query().AnyAsync(x => x.Id == templateId))
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
// Query building
|
||||
|
||||
var query = VariableRepository
|
||||
.Query()
|
||||
.Where(x => x.Template.Id == templateId);
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<VariableDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<VariableDto>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateVariable = await VariableRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateVariable == null)
|
||||
return Problem("No template or template variable found with that id");
|
||||
|
||||
return TemplateMapper.ToDto(templateVariable);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Templates.Create)]
|
||||
public async Task<ActionResult<VariableDto>> CreateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromBody] CreateVariableDto dto
|
||||
)
|
||||
{
|
||||
var template = await TemplateRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == templateId);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
var variable = TemplateMapper.ToEntity(dto);
|
||||
|
||||
variable.Template = template;
|
||||
|
||||
var finalVariable = await VariableRepository.AddAsync(variable);
|
||||
|
||||
return TemplateMapper.ToDto(finalVariable);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Edit)]
|
||||
public async Task<ActionResult<VariableDto>> UpdateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateVariableDto dto
|
||||
)
|
||||
{
|
||||
var templateVariable = await VariableRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateVariable == null)
|
||||
return Problem("No template or template variable found with that id");
|
||||
|
||||
TemplateMapper.Merge(templateVariable, dto);
|
||||
|
||||
await VariableRepository.UpdateAsync(templateVariable);
|
||||
|
||||
return TemplateMapper.ToDto(templateVariable);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateVariable = await VariableRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateVariable == null)
|
||||
return Problem("No template or template variable found with that id");
|
||||
|
||||
await VariableRepository.RemoveAsync(templateVariable);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user