Refactored project to module structure

This commit is contained in:
2026-03-12 22:50:15 +01:00
parent 93de9c5d00
commit 1257e8b950
219 changed files with 1231 additions and 1259 deletions

View File

@@ -0,0 +1,7 @@
namespace Moonlight.Api.Admin.Sys.Versions;
public class FrontendOptions
{
public bool Enabled { get; set; } = true;
public int CacheMinutes { get; set; } = 3;
}

View File

@@ -0,0 +1,12 @@
namespace Moonlight.Api.Admin.Sys.Versions;
// Notes:
// Identifier - This needs to be the branch to clone to build this version if
// you want to use the container helper
public record MoonlightVersion(
string Identifier,
bool IsPreRelease,
bool IsDevelopment,
DateTimeOffset CreatedAt
);

View File

@@ -0,0 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using Moonlight.Shared.Admin.Sys.Versions;
using Riok.Mapperly.Abstractions;
namespace Moonlight.Api.Admin.Sys.Versions;
[Mapper]
[SuppressMessage("Mapper", "RMG020:Source member is not mapped to any target member")]
[SuppressMessage("Mapper", "RMG012:Source member was not found for target member")]
public static partial class VersionMapper
{
public static partial IEnumerable<VersionDto> ToDtos(IEnumerable<MoonlightVersion> versions);
public static partial VersionDto ToDto(MoonlightVersion version);
}

View File

@@ -0,0 +1,7 @@
namespace Moonlight.Api.Admin.Sys.Versions;
public class VersionOptions
{
public bool OfflineMode { get; set; }
public string? CurrentOverride { get; set; }
}

View File

@@ -0,0 +1,135 @@
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Options;
namespace Moonlight.Api.Admin.Sys.Versions;
public partial class VersionService
{
private const string VersionPath = "/app/version";
private const string GiteaServer = "https://git.battlestati.one";
private const string GiteaRepository = "Moonlight-Panel/Moonlight";
private readonly IHttpClientFactory HttpClientFactory;
private readonly IOptions<VersionOptions> Options;
public VersionService(
IOptions<VersionOptions> options,
IHttpClientFactory httpClientFactory
)
{
Options = options;
HttpClientFactory = httpClientFactory;
}
[GeneratedRegex(@"^v(?!1(\.|$))\d+\.[A-Za-z0-9]+(\.[A-Za-z0-9]+)*$")]
private static partial Regex RegexFilter();
public async Task<MoonlightVersion[]> GetVersionsAsync()
{
if (Options.Value.OfflineMode)
return [];
var versions = new List<MoonlightVersion>();
var httpClient = HttpClientFactory.CreateClient();
// Tags
const string tagsPath = $"{GiteaServer}/api/v1/repos/{GiteaRepository}/tags";
await using var tagsJsonStream = await httpClient.GetStreamAsync(tagsPath);
var tagsJson = await JsonNode.ParseAsync(tagsJsonStream);
if (tagsJson != null)
foreach (var node in tagsJson.AsArray())
{
if (node == null)
continue;
var name = node["name"]?.GetValue<string>() ?? "N/A";
var createdAt = node["createdAt"]?.GetValue<DateTimeOffset>() ?? DateTimeOffset.MinValue;
if (!RegexFilter().IsMatch(name))
continue;
versions.Add(new MoonlightVersion(
name,
name.EndsWith('b'),
false,
createdAt
));
}
// Branches
const string branchesPath = $"{GiteaServer}/api/v1/repos/{GiteaRepository}/branches";
await using var branchesJsonStream = await httpClient.GetStreamAsync(branchesPath);
var branchesJson = await JsonNode.ParseAsync(branchesJsonStream);
if (branchesJson != null)
foreach (var node in branchesJson.AsArray())
{
if (node == null)
continue;
var name = node["name"]?.GetValue<string>() ?? "N/A";
var commit = node["commit"];
if (commit == null)
continue;
var createdAt = commit["timestamp"]?.GetValue<DateTimeOffset>() ?? DateTimeOffset.MinValue;
if (!RegexFilter().IsMatch(name))
continue;
versions.Add(new MoonlightVersion(
name,
name.EndsWith('b'),
true,
createdAt
));
}
return versions.ToArray();
}
public async Task<MoonlightVersion> GetInstanceVersionAsync()
{
var knownVersions = await GetVersionsAsync();
string versionIdentifier;
if (!string.IsNullOrWhiteSpace(Options.Value.CurrentOverride))
{
versionIdentifier = Options.Value.CurrentOverride;
}
else
{
if (File.Exists(VersionPath))
versionIdentifier = await File.ReadAllTextAsync(VersionPath);
else
versionIdentifier = "unknown";
}
var version = knownVersions.FirstOrDefault(x => x.Identifier == versionIdentifier);
if (version != null)
return version;
return new MoonlightVersion(
versionIdentifier,
false,
false,
DateTimeOffset.UtcNow
);
}
public async Task<MoonlightVersion?> GetLatestVersionAsync()
{
var versions = await GetVersionsAsync();
return versions
.Where(x => x is { IsDevelopment: false, IsPreRelease: false })
.OrderByDescending(x => x.CreatedAt)
.FirstOrDefault();
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Moonlight.Shared;
using Moonlight.Shared.Admin.Sys.Versions;
namespace Moonlight.Api.Admin.Sys.Versions;
[ApiController]
[Route("api/admin/versions")]
[Authorize(Policy = Permissions.System.Versions)]
public class VersionsController : Controller
{
private readonly VersionService VersionService;
public VersionsController(VersionService versionService)
{
VersionService = versionService;
}
[HttpGet]
public async Task<ActionResult<VersionDto[]>> GetAsync()
{
var versions = await VersionService.GetVersionsAsync();
return VersionMapper.ToDtos(versions).ToArray();
}
[HttpGet("instance")]
public async Task<ActionResult<VersionDto>> GetInstanceAsync()
{
var version = await VersionService.GetInstanceVersionAsync();
return VersionMapper.ToDto(version);
}
[HttpGet("latest")]
public async Task<ActionResult<VersionDto>> GetLatestAsync()
{
var version = await VersionService.GetLatestVersionAsync();
if (version == null)
return Problem("Unable to retrieve latest version", statusCode: 404);
return VersionMapper.ToDto(version);
}
}