using Data.Managers;
using DeepDrftData.Repositories;
using DeepDrftModels.DTOs;
using DeepDrftModels.Entities;
using Microsoft.Extensions.Logging;
using Models.Common;
using NetBlocks.Models;
namespace DeepDrftData;
///
/// SQL-side track service built on the BlazorBlocks Manager base. The layer boundary:
/// TrackRepository outputs entities; this service outputs DTOs via TrackConverter — the
/// single authoritative entity↔DTO conversion path. The ITrackService surface is DTO-typed
/// throughout; the entity never escapes the service layer.
///
/// The base Manager<> surface does not line up with ITrackService by signature (base
/// Add vs Create, base Update→Result vs Update→DTO, base Get/GetPage vs GetAll/GetPaged,
/// base GetById→TDto vs GetById→TDto?), so the query and mutation methods are implemented
/// here over Repository + TrackConverter. Only Delete(long)→Result is inherited unchanged.
///
public class TrackManager
: Manager, ITrackService
{
public TrackManager(
TrackRepository repository,
ILogger> logger)
: base(repository, logger)
{
}
// Explicit impl: base GetById returns ResultContainer (fails on miss); the
// service contract is ResultContainer (pass with null on miss). Return types
// differ, so this cannot be a public overload of the inherited member.
async Task> ITrackService.GetById(long id)
{
try
{
var entity = await Repository.GetByIdAsync(id);
return ResultContainer.CreatePassResult(
entity is null ? null : TrackConverter.Convert(entity));
}
catch (Exception e)
{
return ResultContainer.CreateFailResult(e.Message);
}
}
// Lookup by vault entry key. No base-name conflict (unlike GetById), so this is a plain
// public method. Mirrors the nullable-on-miss shape of ITrackService.GetById.
public async Task> GetByEntryKey(string entryKey)
{
try
{
var entity = await Repository.GetByEntryKeyAsync(entryKey);
return ResultContainer.CreatePassResult(
entity is null ? null : TrackConverter.Convert(entity));
}
catch (Exception e)
{
return ResultContainer.CreateFailResult(e.Message);
}
}
// No base-name conflict, so this is a plain public method. Mirrors the nullable-on-empty
// shape of GetById: pass with null when the library has no tracks.
public async Task> GetRandom(CancellationToken cancellationToken = default)
{
try
{
var entity = await Repository.GetRandomAsync(cancellationToken);
return ResultContainer.CreatePassResult(
entity is null ? null : TrackConverter.Convert(entity));
}
catch (Exception e)
{
return ResultContainer.CreateFailResult(e.Message);
}
}
public async Task>> GetAll()
{
try
{
var entities = await Repository.GetAllAsync();
return ResultContainer>.CreatePassResult(
entities.Select(TrackConverter.Convert).ToList());
}
catch (Exception e)
{
return ResultContainer>.CreateFailResult(e.Message);
}
}
public async Task>> GetPaged(
int pageNumber,
int pageSize,
string? sortColumn,
bool sortDescending,
TrackFilter? filter = null,
CancellationToken cancellationToken = default)
{
try
{
var parameters = new PagingParameters
{
Page = pageNumber,
PageSize = pageSize,
IsDescending = sortDescending,
OrderBy = sortColumn switch
{
"TrackName" => e => e.TrackName,
"Artist" => e => e.Artist,
"Album" => e => (object)(e.Album ?? string.Empty),
"Genre" => e => (object)(e.Genre ?? string.Empty),
"ReleaseDate" => e => (object)(e.ReleaseDate ?? DateOnly.MaxValue),
_ => e => e.Id
}
};
// An all-null filter must produce identical results to no filter, so collapse it to
// null and take the unfiltered base path (preserves backward compatibility).
var effectiveFilter = filter is null || filter.IsEmpty ? null : filter;
var page = effectiveFilter is null
? await Repository.GetPagedAsync(parameters)
: await Repository.GetPagedFilteredAsync(parameters, effectiveFilter, cancellationToken);
var dtoPage = PagedResult.From(page, page.Items.Select(TrackConverter.Convert));
return ResultContainer>.CreatePassResult(dtoPage);
}
catch (Exception e)
{
return ResultContainer>.CreateFailResult(e.Message);
}
}
public async Task>> GetDistinctAlbums(CancellationToken cancellationToken = default)
{
try
{
var albums = await Repository.GetDistinctAlbumsAsync(cancellationToken);
return ResultContainer>.CreatePassResult(albums);
}
catch (Exception e)
{
return ResultContainer>.CreateFailResult(e.Message);
}
}
public async Task>> GetDistinctGenres(CancellationToken cancellationToken = default)
{
try
{
var genres = await Repository.GetDistinctGenresAsync(cancellationToken);
return ResultContainer>.CreatePassResult(genres);
}
catch (Exception e)
{
return ResultContainer>.CreateFailResult(e.Message);
}
}
public async Task> Create(TrackDto newTrack)
{
try
{
var added = await Repository.AddAsync(TrackConverter.Convert(newTrack));
return ResultContainer.CreatePassResult(TrackConverter.Convert(added));
}
catch (Exception e)
{
return ResultContainer.CreateFailResult(e.Message);
}
}
// Explicit impl: base Update returns Result; the service contract returns the persisted
// DTO so the CMS edit flow reads back DB-authoritative values.
async Task> ITrackService.Update(TrackDto track)
{
try
{
await Repository.UpdateAsync(TrackConverter.Convert(track));
var updated = await Repository.GetByIdAsync(track.Id);
return updated is not null
? ResultContainer.CreatePassResult(TrackConverter.Convert(updated))
: ResultContainer.CreateFailResult("Track not found after update.");
}
catch (Exception e)
{
return ResultContainer.CreateFailResult(e.Message);
}
}
// Delete(long) → Result is inherited from Manager<> and satisfies ITrackService.Delete
// by signature. No override.
}