mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
New Series Relation - Edition (#1583)
* Moved UpdateRelatedSeries from controller to SeriesService.cs * Added 2 tests. - UpdateRelatedSeries_ShouldDeletePrequelRelation - UpdateRelatedSeries_ShouldNotAllowDuplicates * Some docs and codestyle nitpicks * Simplified tests and made easier to read * Added 'Editions' series relation * Missing code to properly show the relations in the UI * Create Service for GetRelatedServices * Added unit test. Assert Edition, Prequel and Sequel do not return parent while others do * fixed missing userRating * Add requested changes: - Rename one test - Split one test into two tests
This commit is contained in:
parent
8e79c3b839
commit
7d65dc0530
@ -8,9 +8,10 @@ using API.Data.Repositories;
|
|||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.DTOs.CollectionTags;
|
using API.DTOs.CollectionTags;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
using API.DTOs.Reader;
|
using API.DTOs.SeriesDetail;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using API.Entities.Metadata;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
@ -22,10 +23,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using NSubstitute.Extensions;
|
|
||||||
using NSubstitute.ReceivedExtensions;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Sdk;
|
|
||||||
|
|
||||||
namespace API.Tests.Services;
|
namespace API.Tests.Services;
|
||||||
|
|
||||||
@ -127,6 +125,26 @@ public class SeriesServiceTests
|
|||||||
return fileSystem;
|
return fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UpdateRelatedSeriesDto InstantiateRelationsDto(Series series)
|
||||||
|
{
|
||||||
|
return new UpdateRelatedSeriesDto()
|
||||||
|
{
|
||||||
|
SeriesId = series.Id,
|
||||||
|
Prequels = new List<int>(),
|
||||||
|
Adaptations = new List<int>(),
|
||||||
|
Characters = new List<int>(),
|
||||||
|
Contains = new List<int>(),
|
||||||
|
Doujinshis = new List<int>(),
|
||||||
|
Others = new List<int>(),
|
||||||
|
Sequels = new List<int>(),
|
||||||
|
AlternativeSettings = new List<int>(),
|
||||||
|
AlternativeVersions = new List<int>(),
|
||||||
|
SideStories = new List<int>(),
|
||||||
|
SpinOffs = new List<int>(),
|
||||||
|
Editions = new List<int>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SeriesDetail
|
#region SeriesDetail
|
||||||
@ -1119,4 +1137,221 @@ public class SeriesServiceTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region SeriesRelation
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateRelatedSeries_ShouldAddAllRelations()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
_context.Library.Add(new Library()
|
||||||
|
{
|
||||||
|
AppUsers = new List<AppUser>()
|
||||||
|
{
|
||||||
|
new AppUser()
|
||||||
|
{
|
||||||
|
UserName = "majora2007"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Name = "Test LIb",
|
||||||
|
Type = LibraryType.Book,
|
||||||
|
Series = new List<Series>()
|
||||||
|
{
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Prequels",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Sequels",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related);
|
||||||
|
// Add relations
|
||||||
|
var addRelationDto = InstantiateRelationsDto(series1);
|
||||||
|
addRelationDto.Adaptations.Add(2);
|
||||||
|
addRelationDto.Sequels.Add(3);
|
||||||
|
await _seriesService.UpdateRelatedSeries(addRelationDto);
|
||||||
|
Assert.Equal(2, series1.Relations.Single(s => s.TargetSeriesId == 2).TargetSeriesId);
|
||||||
|
Assert.Equal(3, series1.Relations.Single(s => s.TargetSeriesId == 3).TargetSeriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateRelatedSeries_DeleteAllRelations()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
_context.Library.Add(new Library()
|
||||||
|
{
|
||||||
|
AppUsers = new List<AppUser>()
|
||||||
|
{
|
||||||
|
new AppUser()
|
||||||
|
{
|
||||||
|
UserName = "majora2007"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Name = "Test LIb",
|
||||||
|
Type = LibraryType.Book,
|
||||||
|
Series = new List<Series>()
|
||||||
|
{
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Prequels",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Sequels",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related);
|
||||||
|
// Add relations
|
||||||
|
var addRelationDto = InstantiateRelationsDto(series1);
|
||||||
|
addRelationDto.Adaptations.Add(2);
|
||||||
|
addRelationDto.Sequels.Add(3);
|
||||||
|
await _seriesService.UpdateRelatedSeries(addRelationDto);
|
||||||
|
Assert.Equal(2, series1.Relations.Single(s => s.TargetSeriesId == 2).TargetSeriesId);
|
||||||
|
Assert.Equal(3, series1.Relations.Single(s => s.TargetSeriesId == 3).TargetSeriesId);
|
||||||
|
|
||||||
|
// Remove relations
|
||||||
|
var removeRelationDto = InstantiateRelationsDto(series1);
|
||||||
|
await _seriesService.UpdateRelatedSeries(removeRelationDto);
|
||||||
|
Assert.Empty(series1.Relations.Where(s => s.TargetSeriesId == 1));
|
||||||
|
Assert.Empty(series1.Relations.Where(s => s.TargetSeriesId == 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateRelatedSeries_ShouldNotAllowDuplicates()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
_context.Library.Add(new Library()
|
||||||
|
{
|
||||||
|
AppUsers = new List<AppUser>()
|
||||||
|
{
|
||||||
|
new AppUser()
|
||||||
|
{
|
||||||
|
UserName = "majora2007"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Name = "Test LIb",
|
||||||
|
Type = LibraryType.Book,
|
||||||
|
Series = new List<Series>()
|
||||||
|
{
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Prequels",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related);
|
||||||
|
var relation = new SeriesRelation()
|
||||||
|
{
|
||||||
|
Series = series1,
|
||||||
|
SeriesId = series1.Id,
|
||||||
|
TargetSeriesId = 2, // Target series id
|
||||||
|
RelationKind = RelationKind.Prequel
|
||||||
|
|
||||||
|
};
|
||||||
|
// Manually create a relation
|
||||||
|
series1.Relations.Add(relation);
|
||||||
|
|
||||||
|
// Create a new dto with the previous relation as well
|
||||||
|
var relationDto = InstantiateRelationsDto(series1);
|
||||||
|
relationDto.Adaptations.Add(2);
|
||||||
|
|
||||||
|
await _seriesService.UpdateRelatedSeries(relationDto);
|
||||||
|
// Expected is only one instance of the relation (hence not duping)
|
||||||
|
Assert.Equal(2, series1.Relations.Single(s => s.TargetSeriesId == 2).TargetSeriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetRelatedSeries_EditionPrequelSequel_ShouldNotHaveParent()
|
||||||
|
{
|
||||||
|
await ResetDb();
|
||||||
|
_context.Library.Add(new Library()
|
||||||
|
{
|
||||||
|
AppUsers = new List<AppUser>()
|
||||||
|
{
|
||||||
|
new AppUser()
|
||||||
|
{
|
||||||
|
UserName = "majora2007"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Name = "Test LIb",
|
||||||
|
Type = LibraryType.Book,
|
||||||
|
Series = new List<Series>()
|
||||||
|
{
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Editions",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Prequels",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Sequels",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
},
|
||||||
|
new Series()
|
||||||
|
{
|
||||||
|
Name = "Test Series Adaption",
|
||||||
|
Volumes = new List<Volume>(){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related);
|
||||||
|
// Add relations
|
||||||
|
var addRelationDto = InstantiateRelationsDto(series1);
|
||||||
|
addRelationDto.Editions.Add(2);
|
||||||
|
addRelationDto.Prequels.Add(3);
|
||||||
|
addRelationDto.Sequels.Add(4);
|
||||||
|
addRelationDto.Adaptations.Add(5);
|
||||||
|
await _seriesService.UpdateRelatedSeries(addRelationDto);
|
||||||
|
|
||||||
|
|
||||||
|
Assert.Empty(_seriesService.GetRelatedSeries(1, 2).Result.Parent);
|
||||||
|
Assert.Empty(_seriesService.GetRelatedSeries(1, 3).Result.Parent);
|
||||||
|
Assert.Empty(_seriesService.GetRelatedSeries(1, 4).Result.Parent);
|
||||||
|
Assert.NotEmpty(_seriesService.GetRelatedSeries(1, 5).Result.Parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -416,7 +416,7 @@ public class SeriesController : BaseApiController
|
|||||||
public async Task<ActionResult<RelatedSeriesDto>> GetAllRelatedSeries(int seriesId)
|
public async Task<ActionResult<RelatedSeriesDto>> GetAllRelatedSeries(int seriesId)
|
||||||
{
|
{
|
||||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||||
return Ok(await _unitOfWork.SeriesRepository.GetRelatedSeries(userId, seriesId));
|
return Ok(await _seriesService.GetRelatedSeries(userId, seriesId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -429,52 +429,12 @@ public class SeriesController : BaseApiController
|
|||||||
[HttpPost("update-related")]
|
[HttpPost("update-related")]
|
||||||
public async Task<ActionResult> UpdateRelatedSeries(UpdateRelatedSeriesDto dto)
|
public async Task<ActionResult> UpdateRelatedSeries(UpdateRelatedSeriesDto dto)
|
||||||
{
|
{
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId, SeriesIncludes.Related);
|
if (await _seriesService.UpdateRelatedSeries(dto))
|
||||||
|
{
|
||||||
UpdateRelationForKind(dto.Adaptations, series.Relations.Where(r => r.RelationKind == RelationKind.Adaptation).ToList(), series, RelationKind.Adaptation);
|
return Ok();
|
||||||
UpdateRelationForKind(dto.Characters, series.Relations.Where(r => r.RelationKind == RelationKind.Character).ToList(), series, RelationKind.Character);
|
}
|
||||||
UpdateRelationForKind(dto.Contains, series.Relations.Where(r => r.RelationKind == RelationKind.Contains).ToList(), series, RelationKind.Contains);
|
|
||||||
UpdateRelationForKind(dto.Others, series.Relations.Where(r => r.RelationKind == RelationKind.Other).ToList(), series, RelationKind.Other);
|
|
||||||
UpdateRelationForKind(dto.SideStories, series.Relations.Where(r => r.RelationKind == RelationKind.SideStory).ToList(), series, RelationKind.SideStory);
|
|
||||||
UpdateRelationForKind(dto.SpinOffs, series.Relations.Where(r => r.RelationKind == RelationKind.SpinOff).ToList(), series, RelationKind.SpinOff);
|
|
||||||
UpdateRelationForKind(dto.AlternativeSettings, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeSetting).ToList(), series, RelationKind.AlternativeSetting);
|
|
||||||
UpdateRelationForKind(dto.AlternativeVersions, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeVersion).ToList(), series, RelationKind.AlternativeVersion);
|
|
||||||
UpdateRelationForKind(dto.Doujinshis, series.Relations.Where(r => r.RelationKind == RelationKind.Doujinshi).ToList(), series, RelationKind.Doujinshi);
|
|
||||||
UpdateRelationForKind(dto.Prequels, series.Relations.Where(r => r.RelationKind == RelationKind.Prequel).ToList(), series, RelationKind.Prequel);
|
|
||||||
UpdateRelationForKind(dto.Sequels, series.Relations.Where(r => r.RelationKind == RelationKind.Sequel).ToList(), series, RelationKind.Sequel);
|
|
||||||
|
|
||||||
if (!_unitOfWork.HasChanges()) return Ok();
|
|
||||||
if (await _unitOfWork.CommitAsync()) return Ok();
|
|
||||||
|
|
||||||
|
|
||||||
return BadRequest("There was an issue updating relationships");
|
return BadRequest("There was an issue updating relationships");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to a Service and Unit Test it
|
|
||||||
private void UpdateRelationForKind(ICollection<int> dtoTargetSeriesIds, IEnumerable<SeriesRelation> adaptations, Series series, RelationKind kind)
|
|
||||||
{
|
|
||||||
foreach (var adaptation in adaptations.Where(adaptation => !dtoTargetSeriesIds.Contains(adaptation.TargetSeriesId)))
|
|
||||||
{
|
|
||||||
// If the seriesId isn't in dto, it means we've removed or reclassified
|
|
||||||
series.Relations.Remove(adaptation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, we only have things to add
|
|
||||||
foreach (var targetSeriesId in dtoTargetSeriesIds)
|
|
||||||
{
|
|
||||||
// This ensures we don't allow any duplicates to be added
|
|
||||||
if (series.Relations.SingleOrDefault(r =>
|
|
||||||
r.RelationKind == kind && r.TargetSeriesId == targetSeriesId) !=
|
|
||||||
null) continue;
|
|
||||||
|
|
||||||
series.Relations.Add(new SeriesRelation()
|
|
||||||
{
|
|
||||||
Series = series,
|
|
||||||
SeriesId = series.Id,
|
|
||||||
TargetSeriesId = targetSeriesId,
|
|
||||||
RelationKind = kind
|
|
||||||
});
|
|
||||||
_unitOfWork.SeriesRepository.Update(series);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -22,4 +22,5 @@ public class RelatedSeriesDto
|
|||||||
public IEnumerable<SeriesDto> AlternativeVersions { get; set; }
|
public IEnumerable<SeriesDto> AlternativeVersions { get; set; }
|
||||||
public IEnumerable<SeriesDto> Doujinshis { get; set; }
|
public IEnumerable<SeriesDto> Doujinshis { get; set; }
|
||||||
public IEnumerable<SeriesDto> Parent { get; set; }
|
public IEnumerable<SeriesDto> Parent { get; set; }
|
||||||
|
public IEnumerable<SeriesDto> Editions { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,5 @@ public class UpdateRelatedSeriesDto
|
|||||||
public IList<int> AlternativeSettings { get; set; }
|
public IList<int> AlternativeSettings { get; set; }
|
||||||
public IList<int> AlternativeVersions { get; set; }
|
public IList<int> AlternativeVersions { get; set; }
|
||||||
public IList<int> Doujinshis { get; set; }
|
public IList<int> Doujinshis { get; set; }
|
||||||
|
public IList<int> Editions { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1408,13 +1408,15 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
s.RelationOf.Where(r => r.TargetSeriesId == seriesId
|
s.RelationOf.Where(r => r.TargetSeriesId == seriesId
|
||||||
&& usersSeriesIds.Contains(r.TargetSeriesId)
|
&& usersSeriesIds.Contains(r.TargetSeriesId)
|
||||||
&& r.RelationKind != RelationKind.Prequel
|
&& r.RelationKind != RelationKind.Prequel
|
||||||
&& r.RelationKind != RelationKind.Sequel)
|
&& r.RelationKind != RelationKind.Sequel
|
||||||
|
&& r.RelationKind != RelationKind.Edition)
|
||||||
.Select(sr => sr.Series))
|
.Select(sr => sr.Series))
|
||||||
.RestrictAgainstAgeRestriction(userRating)
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync()
|
.ToListAsync(),
|
||||||
|
Editions = await GetRelatedSeriesQuery(seriesId, usersSeriesIds, RelationKind.Edition, userRating)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,16 @@ public enum RelationKind
|
|||||||
/// Doujinshi or Fan work
|
/// Doujinshi or Fan work
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("Doujinshi")]
|
[Description("Doujinshi")]
|
||||||
Doujinshi = 11
|
Doujinshi = 11,
|
||||||
|
/// <summary>
|
||||||
|
/// This is a UI field only. Not to be used in backend
|
||||||
|
/// </summary>
|
||||||
|
[Description("Parent")]
|
||||||
|
Parent = 12,
|
||||||
|
/// <summary>
|
||||||
|
/// Same story, could be translation, colorization... Different edition of the series
|
||||||
|
/// </summary>
|
||||||
|
[Description("Edition")]
|
||||||
|
Edition = 13
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,14 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Comparators;
|
using API.Comparators;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.DTOs.CollectionTags;
|
using API.DTOs.CollectionTags;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
using API.DTOs.SeriesDetail;
|
using API.DTOs.SeriesDetail;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using API.Entities.Metadata;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -25,7 +27,8 @@ public interface ISeriesService
|
|||||||
Task<bool> UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto);
|
Task<bool> UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto);
|
||||||
Task<bool> UpdateRating(AppUser user, UpdateSeriesRatingDto updateSeriesRatingDto);
|
Task<bool> UpdateRating(AppUser user, UpdateSeriesRatingDto updateSeriesRatingDto);
|
||||||
Task<bool> DeleteMultipleSeries(IList<int> seriesIds);
|
Task<bool> DeleteMultipleSeries(IList<int> seriesIds);
|
||||||
|
Task<bool> UpdateRelatedSeries(UpdateRelatedSeriesDto dto);
|
||||||
|
Task<RelatedSeriesDto> GetRelatedSeries(int userId, int seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeriesService : ISeriesService
|
public class SeriesService : ISeriesService
|
||||||
@ -624,4 +627,76 @@ public class SeriesService : ISeriesService
|
|||||||
_ => "Chapter"
|
_ => "Chapter"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all related series against the passed series Id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="seriesId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<RelatedSeriesDto> GetRelatedSeries(int userId, int seriesId)
|
||||||
|
{
|
||||||
|
return await _unitOfWork.SeriesRepository.GetRelatedSeries(userId, seriesId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the relations attached to the Series. Does not generate associated Sequel/Prequel pairs on target series.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dto"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<bool> UpdateRelatedSeries(UpdateRelatedSeriesDto dto)
|
||||||
|
{
|
||||||
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId, SeriesIncludes.Related);
|
||||||
|
|
||||||
|
UpdateRelationForKind(dto.Adaptations, series.Relations.Where(r => r.RelationKind == RelationKind.Adaptation).ToList(), series, RelationKind.Adaptation);
|
||||||
|
UpdateRelationForKind(dto.Characters, series.Relations.Where(r => r.RelationKind == RelationKind.Character).ToList(), series, RelationKind.Character);
|
||||||
|
UpdateRelationForKind(dto.Contains, series.Relations.Where(r => r.RelationKind == RelationKind.Contains).ToList(), series, RelationKind.Contains);
|
||||||
|
UpdateRelationForKind(dto.Others, series.Relations.Where(r => r.RelationKind == RelationKind.Other).ToList(), series, RelationKind.Other);
|
||||||
|
UpdateRelationForKind(dto.SideStories, series.Relations.Where(r => r.RelationKind == RelationKind.SideStory).ToList(), series, RelationKind.SideStory);
|
||||||
|
UpdateRelationForKind(dto.SpinOffs, series.Relations.Where(r => r.RelationKind == RelationKind.SpinOff).ToList(), series, RelationKind.SpinOff);
|
||||||
|
UpdateRelationForKind(dto.AlternativeSettings, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeSetting).ToList(), series, RelationKind.AlternativeSetting);
|
||||||
|
UpdateRelationForKind(dto.AlternativeVersions, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeVersion).ToList(), series, RelationKind.AlternativeVersion);
|
||||||
|
UpdateRelationForKind(dto.Doujinshis, series.Relations.Where(r => r.RelationKind == RelationKind.Doujinshi).ToList(), series, RelationKind.Doujinshi);
|
||||||
|
UpdateRelationForKind(dto.Prequels, series.Relations.Where(r => r.RelationKind == RelationKind.Prequel).ToList(), series, RelationKind.Prequel);
|
||||||
|
UpdateRelationForKind(dto.Sequels, series.Relations.Where(r => r.RelationKind == RelationKind.Sequel).ToList(), series, RelationKind.Sequel);
|
||||||
|
UpdateRelationForKind(dto.Editions, series.Relations.Where(r => r.RelationKind == RelationKind.Edition).ToList(), series, RelationKind.Edition);
|
||||||
|
|
||||||
|
if (!_unitOfWork.HasChanges()) return true;
|
||||||
|
return await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the provided list to the series. Adds new relations and removes deleted relations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dtoTargetSeriesIds"></param>
|
||||||
|
/// <param name="adaptations"></param>
|
||||||
|
/// <param name="series"></param>
|
||||||
|
/// <param name="kind"></param>
|
||||||
|
private void UpdateRelationForKind(ICollection<int> dtoTargetSeriesIds, IEnumerable<SeriesRelation> adaptations, Series series, RelationKind kind)
|
||||||
|
{
|
||||||
|
foreach (var adaptation in adaptations.Where(adaptation => !dtoTargetSeriesIds.Contains(adaptation.TargetSeriesId)))
|
||||||
|
{
|
||||||
|
// If the seriesId isn't in dto, it means we've removed or reclassified
|
||||||
|
series.Relations.Remove(adaptation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we only have things to add
|
||||||
|
foreach (var targetSeriesId in dtoTargetSeriesIds)
|
||||||
|
{
|
||||||
|
// This ensures we don't allow any duplicates to be added
|
||||||
|
if (series.Relations.SingleOrDefault(r =>
|
||||||
|
r.RelationKind == kind && r.TargetSeriesId == targetSeriesId) !=
|
||||||
|
null) continue;
|
||||||
|
|
||||||
|
series.Relations.Add(new SeriesRelation()
|
||||||
|
{
|
||||||
|
Series = series,
|
||||||
|
SeriesId = series.Id,
|
||||||
|
TargetSeriesId = targetSeriesId,
|
||||||
|
RelationKind = kind
|
||||||
|
});
|
||||||
|
_unitOfWork.SeriesRepository.Update(series);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,4 +14,5 @@ export interface RelatedSeries {
|
|||||||
alternativeVersions: Array<Series>;
|
alternativeVersions: Array<Series>;
|
||||||
doujinshis: Array<Series>;
|
doujinshis: Array<Series>;
|
||||||
parent: Array<Series>;
|
parent: Array<Series>;
|
||||||
}
|
editions: Array<Series>;
|
||||||
|
}
|
||||||
|
@ -13,7 +13,8 @@ export enum RelationKind {
|
|||||||
/**
|
/**
|
||||||
* This is UI only. Backend will generate Parent series for everything but Prequel/Sequel
|
* This is UI only. Backend will generate Parent series for everything but Prequel/Sequel
|
||||||
*/
|
*/
|
||||||
Parent = 12
|
Parent = 12,
|
||||||
|
Edition = 13
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RelationKinds = [
|
export const RelationKinds = [
|
||||||
@ -26,6 +27,7 @@ export const RelationKinds = [
|
|||||||
{text: 'Side Story', value: RelationKind.SideStory},
|
{text: 'Side Story', value: RelationKind.SideStory},
|
||||||
{text: 'Character', value: RelationKind.Character},
|
{text: 'Character', value: RelationKind.Character},
|
||||||
{text: 'Contains', value: RelationKind.Contains},
|
{text: 'Contains', value: RelationKind.Contains},
|
||||||
|
{text: 'Edition', value: RelationKind.Edition},
|
||||||
{text: 'Doujinshi', value: RelationKind.Doujinshi},
|
{text: 'Doujinshi', value: RelationKind.Doujinshi},
|
||||||
{text: 'Other', value: RelationKind.Other},
|
{text: 'Other', value: RelationKind.Other},
|
||||||
];
|
];
|
||||||
|
@ -186,13 +186,13 @@ export class SeriesService {
|
|||||||
return this.httpClient.get<RelatedSeries>(this.baseUrl + 'series/all-related?seriesId=' + seriesId);
|
return this.httpClient.get<RelatedSeries>(this.baseUrl + 'series/all-related?seriesId=' + seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRelationships(seriesId: number, adaptations: Array<number>, characters: Array<number>,
|
updateRelationships(seriesId: number, adaptations: Array<number>, characters: Array<number>,
|
||||||
contains: Array<number>, others: Array<number>, prequels: Array<number>,
|
contains: Array<number>, others: Array<number>, prequels: Array<number>,
|
||||||
sequels: Array<number>, sideStories: Array<number>, spinOffs: Array<number>,
|
sequels: Array<number>, sideStories: Array<number>, spinOffs: Array<number>,
|
||||||
alternativeSettings: Array<number>, alternativeVersions: Array<number>, doujinshis: Array<number>) {
|
alternativeSettings: Array<number>, alternativeVersions: Array<number>, doujinshis: Array<number>, editions: Array<number>) {
|
||||||
return this.httpClient.post(this.baseUrl + 'series/update-related?seriesId=' + seriesId,
|
return this.httpClient.post(this.baseUrl + 'series/update-related?seriesId=' + seriesId,
|
||||||
{seriesId, adaptations, characters, sequels, prequels, contains, others, sideStories, spinOffs,
|
{seriesId, adaptations, characters, sequels, prequels, contains, others, sideStories, spinOffs,
|
||||||
alternativeSettings, alternativeVersions, doujinshis});
|
alternativeSettings, alternativeVersions, doujinshis, editions});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSeriesDetail(seriesId: number) {
|
getSeriesDetail(seriesId: number) {
|
||||||
|
@ -65,6 +65,7 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
|
|||||||
this.setupRelationRows(relations.doujinshis, RelationKind.Doujinshi);
|
this.setupRelationRows(relations.doujinshis, RelationKind.Doujinshi);
|
||||||
this.setupRelationRows(relations.contains, RelationKind.Contains);
|
this.setupRelationRows(relations.contains, RelationKind.Contains);
|
||||||
this.setupRelationRows(relations.parent, RelationKind.Parent);
|
this.setupRelationRows(relations.parent, RelationKind.Parent);
|
||||||
|
this.setupRelationRows(relations.editions, RelationKind.Edition);
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
|
|||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
updateSeries(event: Array<SearchResult | undefined>, relation: RelationControl) {
|
updateSeries(event: Array<SearchResult | undefined>, relation: RelationControl) {
|
||||||
if (event[0] === undefined) {
|
if (event[0] === undefined) {
|
||||||
relation.series = undefined;
|
relation.series = undefined;
|
||||||
@ -131,8 +132,8 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
|
|||||||
seriesSettings.fetchFn = (searchFilter: string) => this.searchService.search(searchFilter).pipe(
|
seriesSettings.fetchFn = (searchFilter: string) => this.searchService.search(searchFilter).pipe(
|
||||||
map(group => group.series),
|
map(group => group.series),
|
||||||
map(items => seriesSettings.compareFn(items, searchFilter)),
|
map(items => seriesSettings.compareFn(items, searchFilter)),
|
||||||
map(series => series.filter(s => s.seriesId !== this.series.id)),
|
map(series => series.filter(s => s.seriesId !== this.series.id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
seriesSettings.compareFn = (options: SearchResult[], filter: string) => {
|
seriesSettings.compareFn = (options: SearchResult[], filter: string) => {
|
||||||
return options.filter(m => this.utilityService.filter(m.name, filter));
|
return options.filter(m => this.utilityService.filter(m.name, filter));
|
||||||
@ -165,10 +166,11 @@ export class EditSeriesRelationComponent implements OnInit, OnDestroy {
|
|||||||
const alternativeSettings = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.AlternativeSetting && item.series !== undefined).map(item => item.series!.id);
|
const alternativeSettings = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.AlternativeSetting && item.series !== undefined).map(item => item.series!.id);
|
||||||
const alternativeVersions = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.AlternativeVersion && item.series !== undefined).map(item => item.series!.id);
|
const alternativeVersions = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.AlternativeVersion && item.series !== undefined).map(item => item.series!.id);
|
||||||
const doujinshis = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.Doujinshi && item.series !== undefined).map(item => item.series!.id);
|
const doujinshis = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.Doujinshi && item.series !== undefined).map(item => item.series!.id);
|
||||||
|
const editions = this.relations.filter(item => (parseInt(item.formControl.value, 10) as RelationKind) === RelationKind.Edition && item.series !== undefined).map(item => item.series!.id);
|
||||||
|
|
||||||
// NOTE: We can actually emit this onto an observable and in main parent, use mergeMap into the forkJoin
|
// NOTE: We can actually emit this onto an observable and in main parent, use mergeMap into the forkJoin
|
||||||
this.seriesService.updateRelationships(this.series.id, adaptations, characters, contains, others, prequels, sequels, sideStories, spinOffs, alternativeSettings, alternativeVersions, doujinshis).subscribe(() => {});
|
this.seriesService.updateRelationships(this.series.id, adaptations, characters, contains, others, prequels, sequels, sideStories, spinOffs, alternativeSettings, alternativeVersions, doujinshis, editions).subscribe(() => {});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@ export class RelationshipPipe implements PipeTransform {
|
|||||||
return 'Spin Off';
|
return 'Spin Off';
|
||||||
case RelationKind.Parent:
|
case RelationKind.Parent:
|
||||||
return 'Parent';
|
return 'Parent';
|
||||||
|
case RelationKind.Edition:
|
||||||
|
return 'Edition'
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
|||||||
private downloadService: DownloadService, private actionService: ActionService,
|
private downloadService: DownloadService, private actionService: ActionService,
|
||||||
public imageSerivce: ImageService, private messageHub: MessageHubService,
|
public imageSerivce: ImageService, private messageHub: MessageHubService,
|
||||||
private readingListService: ReadingListService, public navService: NavService,
|
private readingListService: ReadingListService, public navService: NavService,
|
||||||
private offcanvasService: NgbOffcanvas, @Inject(DOCUMENT) private document: Document,
|
private offcanvasService: NgbOffcanvas, @Inject(DOCUMENT) private document: Document,
|
||||||
private changeDetectionRef: ChangeDetectorRef, private scrollService: ScrollService,
|
private changeDetectionRef: ChangeDetectorRef, private scrollService: ScrollService,
|
||||||
private deviceSerivce: DeviceService
|
private deviceSerivce: DeviceService
|
||||||
) {
|
) {
|
||||||
@ -453,7 +453,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
|||||||
this.seriesMetadata = metadata;
|
this.seriesMetadata = metadata;
|
||||||
this.changeDetectionRef.markForCheck();
|
this.changeDetectionRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.readingListService.getReadingListsForSeries(seriesId).subscribe(lists => {
|
this.readingListService.getReadingListsForSeries(seriesId).subscribe(lists => {
|
||||||
this.readingLists = lists;
|
this.readingLists = lists;
|
||||||
this.changeDetectionRef.markForCheck();
|
this.changeDetectionRef.markForCheck();
|
||||||
@ -482,16 +482,17 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
|||||||
this.relations = [
|
this.relations = [
|
||||||
...relations.prequels.map(item => this.createRelatedSeries(item, RelationKind.Prequel)),
|
...relations.prequels.map(item => this.createRelatedSeries(item, RelationKind.Prequel)),
|
||||||
...relations.sequels.map(item => this.createRelatedSeries(item, RelationKind.Sequel)),
|
...relations.sequels.map(item => this.createRelatedSeries(item, RelationKind.Sequel)),
|
||||||
...relations.sideStories.map(item => this.createRelatedSeries(item, RelationKind.SideStory)),
|
...relations.sideStories.map(item => this.createRelatedSeries(item, RelationKind.SideStory)),
|
||||||
...relations.spinOffs.map(item => this.createRelatedSeries(item, RelationKind.SpinOff)),
|
...relations.spinOffs.map(item => this.createRelatedSeries(item, RelationKind.SpinOff)),
|
||||||
...relations.adaptations.map(item => this.createRelatedSeries(item, RelationKind.Adaptation)),
|
...relations.adaptations.map(item => this.createRelatedSeries(item, RelationKind.Adaptation)),
|
||||||
...relations.contains.map(item => this.createRelatedSeries(item, RelationKind.Contains)),
|
...relations.contains.map(item => this.createRelatedSeries(item, RelationKind.Contains)),
|
||||||
...relations.characters.map(item => this.createRelatedSeries(item, RelationKind.Character)),
|
...relations.characters.map(item => this.createRelatedSeries(item, RelationKind.Character)),
|
||||||
...relations.others.map(item => this.createRelatedSeries(item, RelationKind.Other)),
|
...relations.others.map(item => this.createRelatedSeries(item, RelationKind.Other)),
|
||||||
...relations.alternativeSettings.map(item => this.createRelatedSeries(item, RelationKind.AlternativeSetting)),
|
...relations.alternativeSettings.map(item => this.createRelatedSeries(item, RelationKind.AlternativeSetting)),
|
||||||
...relations.alternativeVersions.map(item => this.createRelatedSeries(item, RelationKind.AlternativeVersion)),
|
...relations.alternativeVersions.map(item => this.createRelatedSeries(item, RelationKind.AlternativeVersion)),
|
||||||
...relations.doujinshis.map(item => this.createRelatedSeries(item, RelationKind.Doujinshi)),
|
...relations.doujinshis.map(item => this.createRelatedSeries(item, RelationKind.Doujinshi)),
|
||||||
...relations.parent.map(item => this.createRelatedSeries(item, RelationKind.Parent)),
|
...relations.parent.map(item => this.createRelatedSeries(item, RelationKind.Parent)),
|
||||||
|
...relations.editions.map(item => this.createRelatedSeries(item, RelationKind.Edition)),
|
||||||
];
|
];
|
||||||
if (this.relations.length > 0) {
|
if (this.relations.length > 0) {
|
||||||
this.hasRelations = true;
|
this.hasRelations = true;
|
||||||
@ -690,7 +691,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
|||||||
this.series = s;
|
this.series = s;
|
||||||
this.changeDetectionRef.detectChanges();
|
this.changeDetectionRef.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.loadSeries(this.seriesId);
|
this.loadSeries(this.seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user