From 616916548a301fdc485bbf982d112ad303a99e57 Mon Sep 17 00:00:00 2001 From: Amelia <77553571+Fesaa@users.noreply.github.com> Date: Sat, 17 May 2025 15:27:18 +0200 Subject: [PATCH] Checkpoint - changing PCs This commit contains broken code --- .../Services/ReadingProfileServiceTest.cs | 11 ++-- API/Controllers/ReadingProfileController.cs | 60 ++++++++++++++++- .../AppUserReadingProfileRepository.cs | 28 ++++++++ API/Helpers/AutoMapperProfiles.cs | 8 +++ API/Services/ReadingProfileService.cs | 66 ++++++++----------- 5 files changed, 128 insertions(+), 45 deletions(-) diff --git a/API.Tests/Services/ReadingProfileServiceTest.cs b/API.Tests/Services/ReadingProfileServiceTest.cs index b7f12bc00..79254a2ff 100644 --- a/API.Tests/Services/ReadingProfileServiceTest.cs +++ b/API.Tests/Services/ReadingProfileServiceTest.cs @@ -112,7 +112,7 @@ public class ReadingProfileServiceTest: AbstractDbTest WidthOverride = 53, }; - await rps.UpdateReadingProfileForSeries(user.Id, series.Id, dto); + await rps.UpdateImplicitReadingProfile(user.Id, series.Id, dto); var profile = await UnitOfWork.AppUserReadingProfileRepository.GetProfileForSeries(user.Id, series.Id); Assert.NotNull(profile); @@ -145,7 +145,8 @@ public class ReadingProfileServiceTest: AbstractDbTest lib.Series.Add(series2); var lib2 = new LibraryBuilder("Manga2").Build(); - var series3 = new SeriesBuilder("A Tropical Fish Yearns for Snow").WithLibraryId(lib2.Id).Build(); + var series3 = new SeriesBuilder("A Tropical Fish Yearns for Snow").Build(); + lib2.Series.Add(series3); user.Libraries.Add(lib2); await UnitOfWork.CommitAsync(); @@ -153,15 +154,15 @@ public class ReadingProfileServiceTest: AbstractDbTest user.UserPreferences.DefaultReadingProfileId = profile3.Id; await UnitOfWork.CommitAsync(); - var p = await rps.GetReadingProfileForSeries(user.Id, series); + var p = await rps.GetReadingProfileForSeries(user.Id, series.Id); Assert.NotNull(p); Assert.Equal("Series Specific", p.Name); - p = await rps.GetReadingProfileForSeries(user.Id, series2); + p = await rps.GetReadingProfileForSeries(user.Id, series2.Id); Assert.NotNull(p); Assert.Equal("Library Specific", p.Name); - p = await rps.GetReadingProfileForSeries(user.Id, series3); + p = await rps.GetReadingProfileForSeries(user.Id, series3.Id); Assert.NotNull(p); Assert.Equal("Global", p.Name); } diff --git a/API/Controllers/ReadingProfileController.cs b/API/Controllers/ReadingProfileController.cs index a9724f178..9f6868247 100644 --- a/API/Controllers/ReadingProfileController.cs +++ b/API/Controllers/ReadingProfileController.cs @@ -1,9 +1,67 @@ +#nullable enable +using System.Threading.Tasks; using API.Data; +using API.DTOs; +using API.Extensions; +using API.Services; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace API.Controllers; -public class ReadingProfileController(ILogger logger, IUnitOfWork unitOfWork): BaseApiController +public class ReadingProfileController(ILogger logger, IUnitOfWork unitOfWork, + IReadingProfileService readingProfileService): BaseApiController { + /// + /// Returns the ReadingProfile that should be applied to the given series, walks up the tree. + /// Series -> Library -> Default + /// + /// + /// + [HttpGet("{seriesId}")] + public async Task> GetProfileForSeries(int seriesId) + { + return Ok(await readingProfileService.GetReadingProfileForSeries(User.GetUserId(), seriesId)); + } + + /// + /// Update, or create the given profile + /// + /// + /// + /// Optionally, from which series the update is called. + /// If set, will delete the implicit reading profile if it exists + /// + /// + [HttpPost] + public async Task UpdateReadingProfile([FromBody] UserReadingProfileDto dto, [FromQuery] int? seriesCtx) + { + if (seriesCtx.HasValue) + { + await readingProfileService.DeleteImplicitForSeries(User.GetUserId(), seriesCtx.Value); + } + + + var success = await readingProfileService.UpdateReadingProfile(User.GetUserId(), dto); + if (!success) return BadRequest(); + + return Ok(); + } + + /// + /// Update the implicit reading profile for a series, creates one if none exists + /// + /// + /// + /// + [HttpPost("series")] + public async Task UpdateReadingProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId) + { + var success = await readingProfileService.UpdateImplicitReadingProfile(User.GetUserId(), seriesId, dto); + if (!success) return BadRequest(); + + return Ok(); + } + } diff --git a/API/Data/Repositories/AppUserReadingProfileRepository.cs b/API/Data/Repositories/AppUserReadingProfileRepository.cs index 0d29404b5..47df09ffb 100644 --- a/API/Data/Repositories/AppUserReadingProfileRepository.cs +++ b/API/Data/Repositories/AppUserReadingProfileRepository.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using API.DTOs; using API.Entities; using API.Extensions.QueryExtensions; using AutoMapper; +using AutoMapper.QueryableExtensions; using Microsoft.EntityFrameworkCore; namespace API.Data.Repositories; @@ -22,8 +24,11 @@ public interface IAppUserReadingProfileRepository { Task> GetProfilesForUser(int userId); Task GetProfileForSeries(int userId, int seriesId); + Task GetProfileDtoForSeries(int userId, int seriesId); Task GetProfileForLibrary(int userId, int libraryId); + Task GetProfileDtoForLibrary(int userId, int libraryId); Task GetProfile(int profileId, ReadingProfileIncludes includes = ReadingProfileIncludes.None); + Task GetProfileDto(int profileId); void Add(AppUserReadingProfile readingProfile); void Update(AppUserReadingProfile readingProfile); @@ -47,6 +52,14 @@ public class AppUserReadingProfileRepository(DataContext context, IMapper mapper .FirstOrDefaultAsync(); } + public async Task GetProfileDtoForSeries(int userId, int seriesId) + { + return await context.AppUserReadingProfile + .Where(rp => rp.UserId == userId && rp.Series.Any(s => s.Id == seriesId)) + .ProjectTo(mapper.ConfigurationProvider) + .FirstOrDefaultAsync(); + } + public async Task GetProfileForLibrary(int userId, int libraryId) { return await context.AppUserReadingProfile @@ -54,6 +67,14 @@ public class AppUserReadingProfileRepository(DataContext context, IMapper mapper .FirstOrDefaultAsync(); } + public async Task GetProfileDtoForLibrary(int userId, int libraryId) + { + return await context.AppUserReadingProfile + .Where(rp => rp.UserId == userId && rp.Libraries.Any(s => s.Id == libraryId)) + .ProjectTo(mapper.ConfigurationProvider) + .FirstOrDefaultAsync(); + } + public async Task GetProfile(int profileId, ReadingProfileIncludes includes = ReadingProfileIncludes.None) { return await context.AppUserReadingProfile @@ -61,6 +82,13 @@ public class AppUserReadingProfileRepository(DataContext context, IMapper mapper .Includes(includes) .FirstOrDefaultAsync(); } + public async Task GetProfileDto(int profileId) + { + return await context.AppUserReadingProfile + .Where(rp => rp.Id == profileId) + .ProjectTo(mapper.ConfigurationProvider) + .FirstOrDefaultAsync(); + } public void Add(AppUserReadingProfile readingProfile) { diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index 75183fdcd..2e9b89735 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -283,6 +283,14 @@ public class AutoMapperProfiles : Profile opt => opt.MapFrom(src => src.BookReaderLayoutMode)); + CreateMap() + .ForMember(dest => dest.BookReaderThemeName, + opt => + opt.MapFrom(src => src.BookThemeName)) + .ForMember(dest => dest.BookReaderLayoutMode, + opt => + opt.MapFrom(src => src.BookReaderLayoutMode)); + CreateMap(); diff --git a/API/Services/ReadingProfileService.cs b/API/Services/ReadingProfileService.cs index 55fe6740c..873bbf5b9 100644 --- a/API/Services/ReadingProfileService.cs +++ b/API/Services/ReadingProfileService.cs @@ -19,9 +19,9 @@ public interface IReadingProfileService /// Series -> Library -> Default /// /// - /// + /// /// - Task GetReadingProfileForSeries(int userId, Series series); + Task GetReadingProfileForSeries(int userId, int seriesId); /// /// Updates, or adds a specific reading profile for a user @@ -32,15 +32,13 @@ public interface IReadingProfileService Task UpdateReadingProfile(int userId, UserReadingProfileDto dto); /// - /// Updates, or adds a specific reading profile for a user for a specific series. - /// If the reading profile is not assigned to the given series (Library, Default fallback), - /// a new implicit reading profile is created + /// Updates the implicit reading profile for a series, creates one if none exists /// /// /// /// /// - Task UpdateReadingProfileForSeries(int userId, int seriesId, UserReadingProfileDto dto); + Task UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto); /// /// Deletes the implicit reading profile for a given series, if it exists @@ -62,21 +60,24 @@ public interface IReadingProfileService } -public class ReadingProfileService(IUnitOfWork unitOfWork): IReadingProfileService +public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService localizationService): IReadingProfileService { - public async Task GetReadingProfileForSeries(int userId, Series series) + public async Task GetReadingProfileForSeries(int userId, int seriesId) { - var seriesProfile = await unitOfWork.AppUserReadingProfileRepository.GetProfileForSeries(userId, series.Id); + var seriesProfile = await unitOfWork.AppUserReadingProfileRepository.GetProfileDtoForSeries(userId, seriesId); if (seriesProfile != null) return seriesProfile; - var libraryProfile = await unitOfWork.AppUserReadingProfileRepository.GetProfileForLibrary(userId, series.Id); + var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId); + if (series == null) throw new KavitaException(await localizationService.Translate(userId, "series-doesnt-exist")); + + var libraryProfile = await unitOfWork.AppUserReadingProfileRepository.GetProfileDtoForLibrary(userId, series.LibraryId); if (libraryProfile != null) return libraryProfile; var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId); if (user == null) throw new UnauthorizedAccessException(); - return await unitOfWork.AppUserReadingProfileRepository.GetProfile(user.UserPreferences.DefaultReadingProfileId); + return await unitOfWork.AppUserReadingProfileRepository.GetProfileDto(user.UserPreferences.DefaultReadingProfileId); } public async Task UpdateReadingProfile(int userId, UserReadingProfileDto dto) @@ -94,45 +95,32 @@ public class ReadingProfileService(IUnitOfWork unitOfWork): IReadingProfileServi return await unitOfWork.CommitAsync(); } - public async Task UpdateReadingProfileForSeries(int userId, int seriesId, UserReadingProfileDto dto) + public async Task UpdateImplicitReadingProfile(int userId, int seriesId, UserReadingProfileDto dto) { - var existingProfile = await unitOfWork.AppUserReadingProfileRepository.GetProfile(dto.Id); + var existingProfile = await unitOfWork.AppUserReadingProfileRepository.GetProfileForSeries(userId, seriesId); - // TODO: Rewrite - var isNew = false; - if (existingProfile == null || existingProfile.Series.All(s => s.Id != seriesId)) - { - var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId); - if (series == null) throw new KeyNotFoundException(); - - existingProfile = new AppUserReadingProfileBuilder(userId) - .WithSeries(series) - .WithImplicit(true) - .Build(); - - isNew = true; - } - - if (existingProfile.UserId != userId) return false; - - UpdateReaderProfileFields(existingProfile, dto); - - if (isNew) - { - unitOfWork.AppUserReadingProfileRepository.Add(existingProfile); - } - else + // Series already had an implicit profile, update it + if (existingProfile is {Implicit: true}) { + UpdateReaderProfileFields(existingProfile, dto); unitOfWork.AppUserReadingProfileRepository.Update(existingProfile); + return await unitOfWork.CommitAsync(); } + var series = await unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId) ?? throw new KeyNotFoundException(); + existingProfile = new AppUserReadingProfileBuilder(userId) + .WithSeries(series) + .WithImplicit(true) + .Build(); + + unitOfWork.AppUserReadingProfileRepository.Add(existingProfile); return await unitOfWork.CommitAsync(); } public async Task DeleteImplicitForSeries(int userId, int seriesId) { var profile = await unitOfWork.AppUserReadingProfileRepository.GetProfileForSeries(userId, seriesId); - if (profile == null) return; + if (profile == null) throw new KavitaException(await localizationService.Translate(userId, "profile-doesnt-exist")); if (!profile.Implicit) return; @@ -144,7 +132,7 @@ public class ReadingProfileService(IUnitOfWork unitOfWork): IReadingProfileServi public async Task DeleteReadingProfile(int userId, int profileId) { var profile = await unitOfWork.AppUserReadingProfileRepository.GetProfile(profileId); - if (profile == null) throw new KeyNotFoundException(); + if (profile == null) throw new KavitaException(await localizationService.Translate(userId, "profile-doesnt-exist")); var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.UserPreferences); if (user == null || profile.UserId != userId) throw new UnauthorizedAccessException();