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,20 @@
using System.Diagnostics.CodeAnalysis;
using Moonlight.Api.Infrastructure.Database.Entities;
using Moonlight.Shared.Admin.Sys.Themes;
using Riok.Mapperly.Abstractions;
namespace Moonlight.Api.Admin.Sys.Themes;
[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 ThemeMapper
{
public static partial IQueryable<ThemeDto> ProjectToDto(this IQueryable<Theme> themes);
public static partial ThemeDto ToDto(Theme theme);
public static partial void Merge([MappingTarget] Theme theme, UpdateThemeDto request);
public static partial Theme ToEntity(CreateThemeDto request);
}

View File

@@ -0,0 +1,12 @@
using VYaml.Annotations;
namespace Moonlight.Api.Admin.Sys.Themes;
[YamlObject]
public partial class ThemeTransferModel
{
public string Name { get; set; }
public string Version { get; set; }
public string Author { get; set; }
public string CssContent { get; set; }
}

View File

@@ -0,0 +1,138 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moonlight.Api.Infrastructure.Database;
using Moonlight.Api.Infrastructure.Database.Entities;
using Moonlight.Api.Shared.Frontend;
using Moonlight.Shared;
using Moonlight.Shared.Admin.Sys.Themes;
using Moonlight.Shared.Shared;
namespace Moonlight.Api.Admin.Sys.Themes;
[ApiController]
[Route("api/admin/themes")]
public class ThemesController : Controller
{
private readonly FrontendService FrontendService;
private readonly DatabaseRepository<Theme> ThemeRepository;
public ThemesController(DatabaseRepository<Theme> themeRepository, FrontendService frontendService)
{
ThemeRepository = themeRepository;
FrontendService = frontendService;
}
[HttpGet]
[Authorize(Policy = Permissions.Themes.View)]
public async Task<ActionResult<PagedData<ThemeDto>>> 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 = ThemeRepository
.Query();
// Filters
if (filterOptions != null)
foreach (var filterOption in filterOptions.Filters)
query = filterOption.Key switch
{
nameof(Theme.Name) =>
query.Where(user => EF.Functions.ILike(user.Name, $"%{filterOption.Value}%")),
nameof(Theme.Version) =>
query.Where(user => EF.Functions.ILike(user.Version, $"%{filterOption.Value}%")),
nameof(Theme.Author) =>
query.Where(user => EF.Functions.ILike(user.Author, $"%{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<ThemeDto>(data, total);
}
[HttpGet("{id:int}")]
[Authorize(Policy = Permissions.Themes.View)]
public async Task<ActionResult<ThemeDto>> GetAsync([FromRoute] int id)
{
var item = await ThemeRepository
.Query()
.FirstOrDefaultAsync(x => x.Id == id);
if (item == null)
return Problem("No theme with this id", statusCode: 404);
return ThemeMapper.ToDto(item);
}
[HttpPost]
[Authorize(Policy = Permissions.Themes.Create)]
public async Task<ActionResult<ThemeDto>> CreateAsync([FromBody] CreateThemeDto request)
{
var theme = ThemeMapper.ToEntity(request);
var finalTheme = await ThemeRepository.AddAsync(theme);
await FrontendService.ResetCacheAsync();
return ThemeMapper.ToDto(finalTheme);
}
[HttpPatch("{id:int}")]
[Authorize(Policy = Permissions.Themes.Edit)]
public async Task<ActionResult<ThemeDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateThemeDto request)
{
var theme = await ThemeRepository
.Query()
.FirstOrDefaultAsync(x => x.Id == id);
if (theme == null)
return Problem("No theme with this id found", statusCode: 404);
ThemeMapper.Merge(theme, request);
await ThemeRepository.UpdateAsync(theme);
await FrontendService.ResetCacheAsync();
return ThemeMapper.ToDto(theme);
}
[HttpDelete("{id:int}")]
[Authorize(Policy = Permissions.Themes.Delete)]
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
{
var theme = await ThemeRepository
.Query()
.FirstOrDefaultAsync(x => x.Id == id);
if (theme == null)
return Problem("No theme with this id found", statusCode: 404);
await ThemeRepository.RemoveAsync(theme);
await FrontendService.ResetCacheAsync();
return NoContent();
}
}

View File

@@ -0,0 +1,74 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moonlight.Api.Infrastructure.Database;
using Moonlight.Api.Infrastructure.Database.Entities;
using Moonlight.Shared;
using Moonlight.Shared.Admin.Sys.Themes;
using VYaml.Serialization;
namespace Moonlight.Api.Admin.Sys.Themes;
[ApiController]
[Route("api/admin/themes")]
[Authorize(Policy = Permissions.Themes.View)]
public class TransferController : Controller
{
private readonly DatabaseRepository<Theme> ThemeRepository;
public TransferController(DatabaseRepository<Theme> themeRepository)
{
ThemeRepository = themeRepository;
}
[HttpGet("{id:int}/export")]
public async Task<ActionResult> ExportAsync([FromRoute] int id)
{
var theme = await ThemeRepository
.Query()
.FirstOrDefaultAsync(x => x.Id == id);
if (theme == null)
return Problem("No theme with that id found", statusCode: 404);
var yml = YamlSerializer.Serialize(new ThemeTransferModel
{
Name = theme.Name,
Author = theme.Author,
CssContent = theme.CssContent,
Version = theme.Version
});
return File(yml.ToArray(), "text/yaml", $"{theme.Name}.yml");
}
[HttpPost("import")]
public async Task<ActionResult<ThemeDto>> ImportAsync()
{
var themeToImport = await YamlSerializer.DeserializeAsync<ThemeTransferModel>(Request.Body);
var existingTheme = await ThemeRepository
.Query()
.FirstOrDefaultAsync(x => x.Name == themeToImport.Name && x.Author == themeToImport.Author);
if (existingTheme == null)
{
var finalTheme = await ThemeRepository.AddAsync(new Theme
{
Name = themeToImport.Name,
Author = themeToImport.Author,
CssContent = themeToImport.CssContent,
Version = themeToImport.Version
});
return ThemeMapper.ToDto(finalTheme);
}
existingTheme.CssContent = themeToImport.CssContent;
existingTheme.Version = themeToImport.Version;
await ThemeRepository.UpdateAsync(existingTheme);
return ThemeMapper.ToDto(existingTheme);
}
}