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();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ namespace MoonlightServers.Api.Infrastructure.Database;
|
||||
public class DataContext : DbContext
|
||||
{
|
||||
public DbSet<Node> Nodes { get; set; }
|
||||
public DbSet<Template> Templates { get; set; }
|
||||
public DbSet<TemplateDockerImage> TemplateDockerImages { get; set; }
|
||||
public DbSet<TemplateVariable> TemplateVariablesVariables { get; set; }
|
||||
|
||||
private readonly IOptions<DatabaseOptions> Options;
|
||||
public DataContext(IOptions<DatabaseOptions> options)
|
||||
@@ -34,5 +37,25 @@ public class DataContext : DbContext
|
||||
modelBuilder.HasDefaultSchema("servers");
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Template>()
|
||||
.ComplexProperty(x => x.FilesConfig, builder => builder.ToJson())
|
||||
.ComplexProperty(x => x.LifecycleConfig, builder => builder.ToJson())
|
||||
.ComplexProperty(x => x.InstallationConfig, builder => builder.ToJson())
|
||||
.ComplexProperty(x => x.MiscellaneousConfig, builder => builder.ToJson());
|
||||
|
||||
// One-to-many: Template => DockerImages
|
||||
modelBuilder.Entity<Template>()
|
||||
.HasMany(t => t.DockerImages)
|
||||
.WithOne(d => d.Template)
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// One-to-one: Template => DefaultDockerImage
|
||||
modelBuilder.Entity<Template>()
|
||||
.HasOne(t => t.DefaultDockerImage)
|
||||
.WithOne()
|
||||
.HasForeignKey<Template>("DefaultDockerImageId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class DbMigrationService : IHostedLifecycleService
|
||||
|
||||
if (migrationNames.Length == 0)
|
||||
{
|
||||
Logger.LogDebug("No pending migrations found");
|
||||
Logger.LogTrace("No pending migrations found");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
public class Template
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Meta
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Author { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Version { get; set; }
|
||||
|
||||
[MaxLength(2048)]
|
||||
public string? UpdateUrl { get; set; }
|
||||
|
||||
[MaxLength(2048)]
|
||||
public string? DonateUrl { get; set; }
|
||||
|
||||
// JSON Options
|
||||
public FilesConfig FilesConfig { get; set; }
|
||||
public LifecycleConfig LifecycleConfig { get; set; }
|
||||
public InstallationConfig InstallationConfig { get; set; }
|
||||
public MiscellaneousConfig MiscellaneousConfig { get; set; }
|
||||
|
||||
// Docker Images
|
||||
public bool AllowUserDockerImageChange { get; set; }
|
||||
public TemplateDockerImage? DefaultDockerImage { get; set; }
|
||||
public List<TemplateDockerImage> DockerImages { get; set; } = new();
|
||||
|
||||
// Variables
|
||||
public List<TemplateVariable> Variables { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
public class TemplateDockerImage
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string ImageName { get; set; }
|
||||
|
||||
public bool SkipPulling { get; set; }
|
||||
|
||||
// Relations
|
||||
public Template Template { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
public class TemplateVariable
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[MaxLength(60)]
|
||||
public string EnvName { get; set; }
|
||||
|
||||
[MaxLength(1024)]
|
||||
public string? DefaultValue { get; set; }
|
||||
|
||||
// Relations
|
||||
public Template Template { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class FilesConfig
|
||||
{
|
||||
public List<ConfigurationFile> ConfigurationFiles { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ConfigurationFile
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string Parser { get; set; } = string.Empty;
|
||||
public List<ConfigurationFileMapping> Mappings { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ConfigurationFileMapping
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string? Value { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class InstallationConfig
|
||||
{
|
||||
public string DockerImage { get; set; } = string.Empty;
|
||||
public string Shell { get; set; } = string.Empty;
|
||||
public string Script { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class LifecycleConfig
|
||||
{
|
||||
public List<StartupCommand> StartupCommands { get; set; } = [];
|
||||
public string StopCommand { get; set; } = string.Empty;
|
||||
public List<string> OnlineLogPatterns { get; set; } = [];
|
||||
}
|
||||
|
||||
public class StartupCommand
|
||||
{
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Command { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class MiscellaneousConfig
|
||||
{
|
||||
public bool UseLegacyStartup { get; set; }
|
||||
}
|
||||
315
MoonlightServers.Api/Infrastructure/Database/Migrations/20260312075719_AddedTemplateEntities.Designer.cs
generated
Normal file
315
MoonlightServers.Api/Infrastructure/Database/Migrations/20260312075719_AddedTemplateEntities.Designer.cs
generated
Normal file
@@ -0,0 +1,315 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260312075719_AddedTemplateEntities")]
|
||||
partial class AddedTemplateEntities
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "10.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("HttpEndpointUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowUserDockerImageChange")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<int?>("DefaultDockerImageId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "FilesConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "ConfigurationFiles", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Parser")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("Path")
|
||||
.IsRequired();
|
||||
|
||||
b2.ComplexCollection(typeof(List<Dictionary<string, object>>), "Mappings", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile.Mappings#ConfigurationFileMapping", b3 =>
|
||||
{
|
||||
b3.IsRequired();
|
||||
|
||||
b3.Property<string>("Key")
|
||||
.IsRequired();
|
||||
|
||||
b3.Property<string>("Value")
|
||||
.IsRequired();
|
||||
});
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("FilesConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "InstallationConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.InstallationConfig#InstallationConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<string>("DockerImage")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Script")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Shell")
|
||||
.IsRequired();
|
||||
|
||||
b1
|
||||
.ToJson("InstallationConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "LifecycleConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.PrimitiveCollection<string>("OnlineLogPatterns")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("StopCommand")
|
||||
.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "StartupCommands", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig.StartupCommands#StartupCommand", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Command")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("DisplayName")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("LifecycleConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "MiscellaneousConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.MiscellaneousConfig#MiscellaneousConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<bool>("UseLegacyStartup");
|
||||
|
||||
b1
|
||||
.ToJson("MiscellaneousConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DefaultDockerImageId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Templates", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("ImageName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("SkipPulling")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateDockerImages", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("EnvName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateVariablesVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", "DefaultDockerImage")
|
||||
.WithOne()
|
||||
.HasForeignKey("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "DefaultDockerImageId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DefaultDockerImage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedTemplateEntities : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TemplateDockerImages",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DisplayName = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
ImageName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
SkipPulling = table.Column<bool>(type: "boolean", nullable: false),
|
||||
TemplateId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TemplateDockerImages", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Templates",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Author = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Version = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
UpdateUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
DonateUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
AllowUserDockerImageChange = table.Column<bool>(type: "boolean", nullable: false),
|
||||
DefaultDockerImageId = table.Column<int>(type: "integer", nullable: true),
|
||||
FilesConfig = table.Column<string>(type: "jsonb", nullable: false),
|
||||
InstallationConfig = table.Column<string>(type: "jsonb", nullable: false),
|
||||
LifecycleConfig = table.Column<string>(type: "jsonb", nullable: false),
|
||||
MiscellaneousConfig = table.Column<string>(type: "jsonb", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Templates", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Templates_TemplateDockerImages_DefaultDockerImageId",
|
||||
column: x => x.DefaultDockerImageId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "TemplateDockerImages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TemplateVariablesVariables",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DisplayName = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
EnvName = table.Column<string>(type: "character varying(60)", maxLength: 60, nullable: false),
|
||||
DefaultValue = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
TemplateId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TemplateVariablesVariables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TemplateVariablesVariables_Templates_TemplateId",
|
||||
column: x => x.TemplateId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Templates",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TemplateDockerImages_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateDockerImages",
|
||||
column: "TemplateId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Templates_DefaultDockerImageId",
|
||||
schema: "servers",
|
||||
table: "Templates",
|
||||
column: "DefaultDockerImageId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TemplateVariablesVariables_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateVariablesVariables",
|
||||
column: "TemplateId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_TemplateDockerImages_Templates_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateDockerImages",
|
||||
column: "TemplateId",
|
||||
principalSchema: "servers",
|
||||
principalTable: "Templates",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_TemplateDockerImages_Templates_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateDockerImages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TemplateVariablesVariables",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Templates",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TemplateDockerImages",
|
||||
schema: "servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
@@ -18,7 +19,7 @@ namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("ProductVersion", "10.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -61,6 +62,250 @@ namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowUserDockerImageChange")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<int?>("DefaultDockerImageId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "FilesConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "ConfigurationFiles", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Parser")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("Path")
|
||||
.IsRequired();
|
||||
|
||||
b2.ComplexCollection(typeof(List<Dictionary<string, object>>), "Mappings", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile.Mappings#ConfigurationFileMapping", b3 =>
|
||||
{
|
||||
b3.IsRequired();
|
||||
|
||||
b3.Property<string>("Key")
|
||||
.IsRequired();
|
||||
|
||||
b3.Property<string>("Value")
|
||||
.IsRequired();
|
||||
});
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("FilesConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "InstallationConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.InstallationConfig#InstallationConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<string>("DockerImage")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Script")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Shell")
|
||||
.IsRequired();
|
||||
|
||||
b1
|
||||
.ToJson("InstallationConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "LifecycleConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.PrimitiveCollection<string>("OnlineLogPatterns")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("StopCommand")
|
||||
.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "StartupCommands", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig.StartupCommands#StartupCommand", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Command")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("DisplayName")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("LifecycleConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "MiscellaneousConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.MiscellaneousConfig#MiscellaneousConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<bool>("UseLegacyStartup");
|
||||
|
||||
b1
|
||||
.ToJson("MiscellaneousConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DefaultDockerImageId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Templates", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("ImageName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("SkipPulling")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateDockerImages", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("EnvName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateVariablesVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", "DefaultDockerImage")
|
||||
.WithOne()
|
||||
.HasForeignKey("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "DefaultDockerImageId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DefaultDockerImage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.3" />
|
||||
<PackageReference Include="Moonlight.Api" Version="2.1.0">
|
||||
<ExcludeAssets>content;contentfiles</ExcludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moonlight.Api;
|
||||
using MoonlightServers.Api.Admin.Nodes;
|
||||
using MoonlightServers.Api.Admin.Templates;
|
||||
using MoonlightServers.Api.Infrastructure.Configuration;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
|
||||
@@ -29,6 +30,10 @@ public class Startup : MoonlightPlugin
|
||||
builder.Services.AddDbContext<DataContext>();
|
||||
builder.Services.AddHostedService<DbMigrationService>();
|
||||
|
||||
builder.Services.AddScoped<TemplateTransferService>();
|
||||
builder.Services.AddScoped<PterodactylEggImportService>();
|
||||
builder.Services.AddScoped<PelicanEggImportService>();
|
||||
|
||||
builder.Services.AddSingleton<NodeService>();
|
||||
|
||||
var nodeTokenOptions = new NodeTokenOptions();
|
||||
|
||||
Reference in New Issue
Block a user