From a1c9e3807bfe795d3f21c35ffc6f7f831007f859 Mon Sep 17 00:00:00 2001 From: sherl Date: Wed, 28 Jan 2026 05:18:03 +0100 Subject: [PATCH] feat: add album and miscellaneous controllers with album mapping --- Controllers/AlbumController.cs | 135 +++++++++++++++++++++++++ Controllers/MiscController.cs | 153 +++++++++++++++++++++++++++++ Mapping/AlbumMapping.cs | 27 +++++ Tools/GeneralUseHelperFunctions.cs | 52 ++++++++++ 4 files changed, 367 insertions(+) create mode 100644 Controllers/AlbumController.cs create mode 100644 Controllers/MiscController.cs create mode 100644 Mapping/AlbumMapping.cs diff --git a/Controllers/AlbumController.cs b/Controllers/AlbumController.cs new file mode 100644 index 0000000..ff3cca7 --- /dev/null +++ b/Controllers/AlbumController.cs @@ -0,0 +1,135 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.FileProviders; +using Shadow.Data; +using Shadow.DTOs; +using Shadow.Entities; +using Shadow.Mapping; +using Shadow.Tools; + +namespace Shadow.Controllers; + +[ApiController] +[Route("rest")] +[Produces("application/json")] +public class AlbumController : ControllerBase +{ + + private readonly ApplicationDbContext db; + private readonly GeneralUseHelpers guhf; + + public AlbumController(ApplicationDbContext _db, GeneralUseHelpers _guhf) + { + db = _db; + guhf = _guhf; + } + + [HttpGet("getCoverArt")] + [HttpGet("getCoverArt.view")] + [HttpPost("getCoverArt.view")] + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public async Task GetCoverArt() + { + string thumbnailPath = db.Globals + .First(g => g.Key == "musicThumbnailPath").Value!; + + IFileInfo fileInfo = new PhysicalFileProvider(thumbnailPath) + .GetFileInfo("default.png"); + + if (!fileInfo.Exists) return NotFound(); + return PhysicalFile(fileInfo.PhysicalPath!, "image/png"); + } + + [HttpGet("getAlbumList2")] + [ProducesResponseType(200)] + public async Task GetAlbumList2() + { + // First, check if user is authorized + User? user = await guhf.GetUserFromParams(Request); + + if (user == null) + { + // Craft an error + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + Status = "failed", + error = new ErrorDTO() + } + }); + } + + List albumsDTO = []; + List albums = db.Albums + .Where(a => a.State != 1) + .Include(a => a.Artist) + .Include(a => a.Songs) + .ToList(); + + foreach (Album al in albums) + { + albumsDTO.Add( + AlbumMapping.ToAlbumViewShort(al) + ); + } + + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + albumList2 = new AlbumList2DTO + { + album = albumsDTO + } + } + }); + } + + [HttpPost("getAlbumList2.view")] + [ProducesResponseType(200)] + public async Task GetAlbumList2Form() + { + // First, check if user is authorized + User? user = await guhf.GetUserFromForm(Request); + + if (user == null) + { + // Craft an error + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + Status = "failed", + error = new ErrorDTO() + } + }); + } + + List albumsDTO = []; + List albums = db.Albums + .Where(a => a.State != 1) + .Include(a => a.Artist) + .Include(a => a.Songs) + .ToList(); + + foreach (Album al in albums) + { + albumsDTO.Add( + AlbumMapping.ToAlbumViewShort(al) + ); + } + + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + albumList2 = new AlbumList2DTO + { + album = albumsDTO + } + } + }); + } +} diff --git a/Controllers/MiscController.cs b/Controllers/MiscController.cs new file mode 100644 index 0000000..613c69b --- /dev/null +++ b/Controllers/MiscController.cs @@ -0,0 +1,153 @@ +using Microsoft.AspNetCore.Mvc; +using Shadow.Data; +using Shadow.DTOs; +using Shadow.Entities; +using Shadow.Tools; + +namespace Shadow.Controllers; + +[ApiController] +[Route("rest")] +[Produces("application/json")] +public class MiscController : ControllerBase +{ + + private readonly ApplicationDbContext db; + private readonly GeneralUseHelpers guhf; + + public MiscController(ApplicationDbContext _db, GeneralUseHelpers _guhf) + { + db = _db; + guhf = _guhf; + } + + [HttpGet("ping")] + [ProducesResponseType(200)] + public async Task Ping() + { + User? user = await guhf.GetUserFromParams(Request); + + // Wrong credentials? + if (user == null) + { + // Craft an error + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + Status = "failed", + error = new ErrorDTO + { + code = 40, + message = "Wrong username or password." + } + } + }); + } + + return Ok(new ResponseWrapper { + SubsonicResponse = new SubsonicResponseDTO() + }); + } + + [HttpPost("ping.view")] + [ProducesResponseType(200)] + public async Task PingForm() + { + User? user = await guhf.GetUserFromForm(Request); + + // Wrong credentials? + if (user == null) { + // Craft an error + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO { + Status = "failed", + error = new ErrorDTO { + code = 40, + message = "Wrong username or password." + } + } + }); + } + + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO() + }); + } + + [HttpGet("getUser")] + [ProducesResponseType(200)] + public async Task GetUser() + { + User? user = await guhf.GetUserFromParams(Request); + + // Wrong credentials? + if (user == null) + { + // Craft an error + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + Status = "failed", + error = new ErrorDTO + { + code = 40, + message = "Wrong username or password." + } + } + }); + } + + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + userPing = new UserPingDTO + { + username = user.Name, + adminRole = user.IsAdmin() + } + } + }); + } + + [HttpPost("getUser.view")] + [ProducesResponseType(200)] + public async Task GetUserForm() + { + User? user = await guhf.GetUserFromForm(Request); + + // Wrong credentials? + if (user == null) + { + // Craft an error + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + Status = "failed", + error = new ErrorDTO + { + code = 40, + message = "Wrong username or password." + } + } + }); + } + + return Ok(new ResponseWrapper + { + SubsonicResponse = new SubsonicResponseDTO + { + userPing = new UserPingDTO + { + username = user.Name, + adminRole = user.IsAdmin() + } + } + }); + } +} diff --git a/Mapping/AlbumMapping.cs b/Mapping/AlbumMapping.cs new file mode 100644 index 0000000..a513ea0 --- /dev/null +++ b/Mapping/AlbumMapping.cs @@ -0,0 +1,27 @@ +using Shadow.DTOs; +using Shadow.Entities; + +namespace Shadow.Mapping; + +public static class AlbumMapping +{ + public static AlbumViewShortDTO ToAlbumViewShort(Album album) + { + AlbumViewShortDTO dto = new AlbumViewShortDTO + { + id = $"{album.Id}", + name = album.Name, + artist = album.Artist?.Name ?? "[Unknown Artist]", + artistId = $"{album.ArtistId}", + coverArt = "default.png", + songCount = album.Songs.Count, + duration = album.Songs.Sum(s => s.Duration), + playCount = 0, + year = 0, + genre = "unknown", + // played = "never" + }; + + return dto; + } +} diff --git a/Tools/GeneralUseHelperFunctions.cs b/Tools/GeneralUseHelperFunctions.cs index 3e25a72..29d1cd5 100644 --- a/Tools/GeneralUseHelperFunctions.cs +++ b/Tools/GeneralUseHelperFunctions.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Shadow.Data; using Shadow.Entities; using System.Text; +using System.Xml.Serialization; namespace Shadow.Tools; @@ -46,4 +47,55 @@ public class GeneralUseHelpers(ApplicationDbContext? db = null, IConfiguration? return "{" + resultJson[..^2] + "}"; } + + public async Task GetUserFromForm(HttpRequest request) + { + User? user = null; + try + { + string username = request.Form["u"]!; + string saltedPassword = request.Form["t"]!; + string sesame = request.Form["s"]!; + + User? foundUser = _db?.Users + .FirstOrDefault(u => u.NormalizedName == username.ToLower()); + + if (foundUser == null) + return user; + + string resaltedPassword = MetadataExtractor.GetStringMD5($"{foundUser.Password}{sesame}"); + if (resaltedPassword == saltedPassword) + user = foundUser; + } + catch + { + user = null; + } + + return user; + } + + public async Task GetUserFromParams(HttpRequest request) + { + User? user = null; + try + { + string username = request.Query["u"]!; + string saltedPassword = request.Query["t"]!; + string sesame = request.Query["s"]!; + + User foundUser = _db!.Users + .FirstOrDefault(u => u.NormalizedName == username.ToLower())!; + + string resaltedPassword = MetadataExtractor.GetStringMD5($"{foundUser.Password}{sesame}"); + if (resaltedPassword == saltedPassword) + user = foundUser; + } + catch + { + user = null; + } + + return user; + } }