Compare commits
2 Commits
c2248b6e8d
...
993f48d878
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
993f48d878 | ||
|
|
1a18a31fdd |
30
.dockerignore
Normal file
30
.dockerignore
Normal file
@ -0,0 +1,30 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
!**/.gitignore
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.git/packed-refs
|
||||
!.git/refs/heads/**
|
||||
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,6 +30,7 @@ x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
|
||||
57
Common.Domain/Abstractions/Entity.cs
Normal file
57
Common.Domain/Abstractions/Entity.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using MediatR;
|
||||
namespace Common.Domain.Abstractions;
|
||||
|
||||
public abstract class Entity
|
||||
{
|
||||
private int? _requestedHashCode;
|
||||
|
||||
public virtual Guid Id { get; protected set; }
|
||||
|
||||
private List<INotification> _domainEvents = [];
|
||||
public IReadOnlyCollection<INotification> DomainEvents => _domainEvents.AsReadOnly();
|
||||
|
||||
public void AddDomainEvent(INotification eventItem) => _domainEvents.Add(eventItem);
|
||||
public void RemoveDomainEvent(INotification eventItem) => _domainEvents.Remove(eventItem);
|
||||
public void ClearDomainEvents() => _domainEvents.Clear();
|
||||
|
||||
public bool IsTransient() => Id == default;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj == null || !(obj is Entity))
|
||||
return false;
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
|
||||
if (GetType() != obj.GetType())
|
||||
return false;
|
||||
|
||||
Entity item = (Entity)obj;
|
||||
|
||||
if (item.IsTransient() || IsTransient())
|
||||
return false;
|
||||
else
|
||||
return item.Id == Id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (!IsTransient())
|
||||
{
|
||||
if (!_requestedHashCode.HasValue)
|
||||
_requestedHashCode = Id.GetHashCode() ^ 31; // XOR for random distribution (http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx)
|
||||
return _requestedHashCode.Value;
|
||||
}
|
||||
else
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(Entity left, Entity right) =>
|
||||
Equals(left, null) ? Equals(right, null) : left.Equals(right);
|
||||
|
||||
public static bool operator !=(Entity left, Entity right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
3
Common.Domain/Abstractions/IAggregateRoot.cs
Normal file
3
Common.Domain/Abstractions/IAggregateRoot.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Common.Domain.Abstractions;
|
||||
|
||||
public interface IAggregateRoot { }
|
||||
7
Common.Domain/Class1.cs
Normal file
7
Common.Domain/Class1.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Common.Domain
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
13
Common.Domain/Common.Domain.csproj
Normal file
13
Common.Domain/Common.Domain.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,8 @@
|
||||
using Modules.Library.Application.Interfaces;
|
||||
|
||||
namespace Modules.Library.Application.Gateways;
|
||||
|
||||
public interface IAnimeUserGateway
|
||||
{
|
||||
public IAnimeUser GetUserById(Guid Id);
|
||||
}
|
||||
36
Modules.Library.Application/Gateways/IRepository.cs
Normal file
36
Modules.Library.Application/Gateways/IRepository.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Modules.Library.Domain.Entities;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Modules.Library.Application.Gateways;
|
||||
|
||||
public interface IRepository<T, TKey>
|
||||
{
|
||||
Task<List<T>> GetAllAsync();
|
||||
|
||||
Task<T> GetByIdAsync(TKey id);
|
||||
Task<T?> GetByIdOrDefaultAsync(TKey id);
|
||||
|
||||
Task<List<T>> GetRangeByIdsAsync(List<TKey> ids);
|
||||
|
||||
Task<T> GetFirstWhere(Expression<Func<T, bool>> predicate);
|
||||
Task<T?> GetFirstOrDefaultWhere(Expression<Func<T, bool>> predicate);
|
||||
|
||||
Task<List<T>> GetWhere(Expression<Func<T, bool>> predicate);
|
||||
|
||||
Task<bool> AnyWhere(Expression<Func<T, bool>> predicate);
|
||||
/*
|
||||
Task AddAsync(T entity, IUser user);
|
||||
|
||||
Task<bool> UpdateAsync(T entity, IUser user);
|
||||
|
||||
Task<bool> DeleteAsync(T entity, IUser user);
|
||||
*/
|
||||
Task AddAsync(T entity);
|
||||
|
||||
Task<bool> UpdateAsync(T entity);
|
||||
|
||||
Task<bool> DeleteAsync(T entity);
|
||||
}
|
||||
|
||||
public interface IRepository<T> : IRepository<T, string> where T : Entity { }
|
||||
//public interface IRepository<T> : IRepository<T, string> { }
|
||||
8
Modules.Library.Application/Gateways/IUserGateway.cs
Normal file
8
Modules.Library.Application/Gateways/IUserGateway.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using Modules.Library.Application.Interfaces;
|
||||
|
||||
namespace Modules.Library.Application.Gateways;
|
||||
|
||||
public interface IUserGateway
|
||||
{
|
||||
public IUser GetUserById(Guid Id);
|
||||
}
|
||||
16
Modules.Library.Application/Interfaces/IAnimeUser.cs
Normal file
16
Modules.Library.Application/Interfaces/IAnimeUser.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Modules.Library.Application.Interfaces;
|
||||
|
||||
public interface IAnimeUser : IUser
|
||||
{
|
||||
public bool CanCreateTitle();
|
||||
public bool CanCreateSeason();
|
||||
public bool CanCreateEpisode();
|
||||
|
||||
public bool CanEditTitle();
|
||||
public bool CanEditSeason();
|
||||
public bool CanEditEpisode();
|
||||
|
||||
public bool CanRemoveTitle();
|
||||
public bool CanRemoveSeason();
|
||||
public bool CanRemoveEpisode();
|
||||
}
|
||||
13
Modules.Library.Application/Interfaces/IUser.cs
Normal file
13
Modules.Library.Application/Interfaces/IUser.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Modules.Library.Application.Interfaces;
|
||||
|
||||
public interface IUser
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public bool CanAddMediaContent();
|
||||
public bool CanEditMediaContent();
|
||||
public bool CanRemoveMediaContent();
|
||||
|
||||
public bool CanAddMediaInfo();
|
||||
public bool CanEditMediaInfo();
|
||||
public bool CanRemoveMediaInfo();
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Modules.Library.Domain\Modules.Library.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
17
Modules.Library.Application/ServiceCollectionExtensions.cs
Normal file
17
Modules.Library.Application/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Modules.Library.Application.Services.MediaContent;
|
||||
|
||||
namespace Modules.Library.Application;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<AnimeTitleService>();
|
||||
services.AddScoped<AnimeSeasonService>();
|
||||
services.AddScoped<AnimeEpisodeService>();
|
||||
|
||||
services.AddScoped<AnimeEpisodeService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
15
Modules.Library.Application/Services/GenreService.cs
Normal file
15
Modules.Library.Application/Services/GenreService.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using Modules.Library.Domain.Interactors;
|
||||
|
||||
namespace Modules.Library.Application.Services;
|
||||
|
||||
public class GenreService(IGenreGateway genreGateway)
|
||||
{
|
||||
private readonly GenreInteractor _genreInteractor = new(genreGateway);
|
||||
//public async Task<Guid> AddGenre(string name)
|
||||
public async Task<Guid> Add(string name) => await _genreInteractor.Create(name);
|
||||
|
||||
public async Task Edit(Guid genreId, string name) => await _genreInteractor.Edit(genreId, name);
|
||||
|
||||
public async Task Remove(Guid genreId) => await _genreInteractor.Delete(genreId);
|
||||
}
|
||||
21
Modules.Library.Application/Services/LanguageService.cs
Normal file
21
Modules.Library.Application/Services/LanguageService.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using Modules.Library.Domain.Interactors;
|
||||
|
||||
namespace Modules.Library.Application.Services;
|
||||
|
||||
//public class LanguageService(ILanguageGateway languageGateway, IUserGateway userGateway)
|
||||
public class LanguageService(ILanguageGateway languageGateway)
|
||||
{
|
||||
private readonly LanguageInteractor _languageInteractor = new(languageGateway);
|
||||
//public async Task<Guid> AddGenre(string name)
|
||||
//public async Task Add(IUser user, string codeIso2, string name, Guid? iconId)
|
||||
public async Task<Guid> Add(string codeIso2, string name, Guid? iconId) =>
|
||||
await _languageInteractor.Create(codeIso2, name, iconId);
|
||||
|
||||
//public async Task Edit(IUser user, Guid id, string name, Guid? iconId)
|
||||
public async Task Edit(Guid id, string name, Guid? iconId) =>
|
||||
await _languageInteractor.Edit(id, name, iconId);
|
||||
|
||||
//public async Task Remove(IUser user, Guid id)
|
||||
public async Task Remove(Guid id) => await _languageInteractor.Delete(id);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
using Modules.Library.Application.Gateways;
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
namespace Modules.Library.Application.Services.MediaContent;
|
||||
|
||||
public class AnimeEpisodeService(IAnimeTitleGateway titleRepository)
|
||||
{
|
||||
// public async Task Edit(string titleId, string? seasonId, string episodeId, int? number, TimeSpan? duration)
|
||||
// {
|
||||
// var title = await titleRepository.GetByIdAsync(episodeId);
|
||||
// var episode = (string.IsNullOrWhiteSpace(seasonId) ? title.Items.OfType<AnimeEpisode>() :
|
||||
// title.Items.OfType<AnimeSeason>().FirstOrDefault(q => q.Id == seasonId)?.Episodes)?.FirstOrDefault(q => q.Id == episodeId);
|
||||
// //if (episode == null) throw new EpisodeNotFoundException;
|
||||
// if (episode == null) throw new Exception("EpisodeNotFound");
|
||||
// episode.SetNumber(number);
|
||||
// if (duration.HasValue) episode.SetDuration(duration.Value);
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
namespace Modules.Library.Application.Services.MediaContent;
|
||||
|
||||
public class AnimeSeasonService(IAnimeTitleGateway titleGateway)
|
||||
{
|
||||
// public async Task AddEpisode(Guid titleId, Guid seasonId, AnimeEpisodeType episodeType)
|
||||
// {
|
||||
// await StructureAction(titleId, seasonId, (title, season) =>
|
||||
// {
|
||||
// var lastOrder = season.Items.OrderByDescending(q => q.Order).FirstOrDefault()?.Order;
|
||||
// var episode = AnimeEpisode.New(episodeType);
|
||||
// season.AddItem(episode);
|
||||
// episode.SetOrder(lastOrder.HasValue ? lastOrder.Value + 1 : 0);
|
||||
// episode.SetNumber(season.Items.OfType<AnimeEpisode>().Select(q => q.Number).DefaultIfEmpty(0).Max() + 1);
|
||||
// });
|
||||
// }
|
||||
|
||||
// public async Task AddEpisodeAsVariant(Guid titleId, Guid seasonId, AnimeEpisodeType episodeType, uint order)
|
||||
// {
|
||||
// await StructureAction(titleId, seasonId, (title, season) =>
|
||||
// {
|
||||
// var lastVariant = season.Items.OrderByDescending(q => q.Variant).FirstOrDefault(q => q.Order == order)?.Variant ??
|
||||
// throw new Exception($"Could not find episode with order [{order}]");
|
||||
// var episode = AnimeEpisode.New(episodeType);
|
||||
// season.AddItem(episode);
|
||||
// episode.SetOrder(order);
|
||||
// episode.SetVariant(lastVariant + 1);
|
||||
// episode.SetNumber(season.Items.Where(q => q.Order == order).OfType<AnimeEpisode>().Select(q => q.Number).FirstOrDefault() ?? 0);
|
||||
// });
|
||||
// }
|
||||
|
||||
// public async Task RemoveEpisode(Guid titleId, Guid seasonId, Guid episodeId)
|
||||
// {
|
||||
// await StructureAction(titleId, seasonId, (title, season) =>
|
||||
// {
|
||||
// var episode = season.Items.OrderByDescending(q => q.Variant).FirstOrDefault(q => q.Id == episodeId) ??
|
||||
// throw new Exception($"Could not find episode with Id [{episodeId}]");
|
||||
// //season.RemoveItem(episode);
|
||||
// episode.SetDeleted();
|
||||
// });
|
||||
// }
|
||||
|
||||
// private async Task StructureAction(Guid titleId, Guid seasonId, Action<AnimeTitle, AnimeSeason> action)
|
||||
// {
|
||||
// var title = await titleGateway.GetById(titleId);
|
||||
// var season = await seasonRepository.GetById(seasonId);
|
||||
// action.Invoke(title, season);
|
||||
// season.CheckIfCompleted();
|
||||
// title.CheckIfCompleted();
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// public async Task Edit(Guid seasonId, int? number, string? director, string? originCountry, TimeSpan? expirationTime)
|
||||
// {
|
||||
// var season = await seasonRepository.GetById(seasonId);
|
||||
// season.SetNumber(number);
|
||||
// season.SetDirector(director);
|
||||
// season.SetOriginCountry(originCountry);
|
||||
// season.SetExpirationTime(expirationTime ?? TimeSpan.Zero);
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
using Modules.Library.Domain.EntityBuilders;
|
||||
using Modules.Library.Domain.Interactors.MediaContent.Anime.Title;
|
||||
|
||||
namespace Modules.Library.Application.Services.MediaContent;
|
||||
|
||||
public class AnimeTitleService(IAnimeTitleGateway titleGateway, ILanguageGateway languageGateway, IGenreGateway genreGateway)
|
||||
{
|
||||
private readonly CreateInteractor createInteractor = new (titleGateway, languageGateway, genreGateway);
|
||||
public async Task<Guid> New(string nameOriginal, Guid nameOriginalLanguageId) =>
|
||||
await createInteractor.Create(nameOriginal, nameOriginalLanguageId);
|
||||
|
||||
public async Task<Guid> AddSeason(Guid titleId, string nameOriginal, Guid nameOriginalLanguageId) =>
|
||||
await createInteractor.CreateAnimeSeason(titleId, nameOriginal, nameOriginalLanguageId);
|
||||
|
||||
public async Task<Guid> AddEpisode(Guid titleId, Guid? seasonId, AnimeEpisodeType episodeType, string nameOriginal, Guid nameOriginalLanguageId)
|
||||
{
|
||||
if (seasonId.HasValue)
|
||||
return await createInteractor.CreateAnimeEpisode(titleId, seasonId.Value, episodeType, nameOriginal, nameOriginalLanguageId);
|
||||
else
|
||||
return await createInteractor.CreateAnimeEpisode(titleId, episodeType, nameOriginal, nameOriginalLanguageId);
|
||||
}
|
||||
/*
|
||||
public async Task AddEpisodeAsVariant(string titleId, string seasonId, AnimeEpisodeType episodeType, ushort order)
|
||||
{
|
||||
var title = await titleGateway.GetByIdAsync(titleId);
|
||||
var season = title.Items.OfType<AnimeSeason>().FirstOrDefault(q => q.Id == seasonId) ??
|
||||
throw new Exception("NotFound");
|
||||
season.AddEpisodeAsVariant(episodeType, order);
|
||||
}
|
||||
*/
|
||||
|
||||
//public async Task RemoveItem(string titleId, string itemId)
|
||||
//{
|
||||
// var title = await titleGateway.GetByIdAsync(titleId);
|
||||
// var item = title.Items.FirstOrDefault(q => q.Id == itemId) ??
|
||||
// throw new Exception("NotFound");
|
||||
// if (item is AnimeSeason season) title.RemoveSeason(season);
|
||||
// else if (item is AnimeEpisode episode) title.RemoveEpisode(episode);
|
||||
//}
|
||||
|
||||
//public async Task Edit(string seasonId, TimeSpan? expirationTime)
|
||||
//{
|
||||
// var title = await titleGateway.GetByIdAsync(seasonId);
|
||||
// title.SetExpirationTime(expirationTime ?? TimeSpan.Zero);
|
||||
//}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
|
||||
namespace Modules.Library.Application.Services.MediaContent.CommonProperties;
|
||||
|
||||
public class CommonPropertiesService(ICommonPropertiesGateway commonPropertiesGateway, IGenreGateway genreGateway)
|
||||
{
|
||||
/*
|
||||
|
||||
public async Task SetNameValue(Guid commonPropertiesId, Guid nameId, string value)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.SetNameValue(nameId, value);
|
||||
}
|
||||
public async Task RemoveName(Guid commonPropertiesId, Guid nameId)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.RemoveName(nameId);
|
||||
}
|
||||
|
||||
|
||||
public async Task AddPreview(Guid commonPropertiesId, string url, MediaInfoType type)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.SetPreview(url, type);
|
||||
}
|
||||
public async Task EditPreview(Guid commonPropertiesId, string url, MediaInfoType type) =>
|
||||
await AddPreview(commonPropertiesId, url, type);
|
||||
public async Task DeletePreview(Guid commonPropertiesId)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.DeletePreview();
|
||||
}
|
||||
|
||||
public async Task AddDescription(Guid commonPropertiesId, string value, bool isOriginal = false)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
switch (isOriginal)
|
||||
{
|
||||
case true:
|
||||
commonProperties.AddOriginalDescription(value);
|
||||
break;
|
||||
default:
|
||||
commonProperties.AddNotOriginalDescription(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
public async Task SetDescriptionValue(Guid commonPropertiesId, Guid descriptionId, string value)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.SetDescriptionValue(descriptionId, value);
|
||||
}
|
||||
public async Task RemoveDescription(Guid commonPropertiesId, Guid descriptionId)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.RemoveDescription(descriptionId);
|
||||
}
|
||||
|
||||
public async Task AddGenre(Guid commonPropertiesId, Guid genreId, decimal? value = null)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.AddGenre(genreId, value);
|
||||
}
|
||||
public async Task SetGenreProportion(Guid commonPropertiesId, Guid genreProportionItemId, decimal? value = null)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.SetGenreProportion(genreProportionItemId, value);
|
||||
}
|
||||
public async Task RemoveGenre(Guid commonPropertiesId, Guid genreId)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.RemoveGenre(genreId);
|
||||
}
|
||||
|
||||
public async Task AddRelatedContent(Guid commonPropertiesId, string url, MediaInfoType type)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.AddRelatedContent(url, type);
|
||||
}
|
||||
public async Task EditRelatedContent(Guid commonPropertiesId, Guid relatedContentId, string url, MediaInfoType type)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.EditRelatedContent(relatedContentId, url, type);
|
||||
}
|
||||
public async Task RemoveRelatedContent(Guid commonPropertiesId, Guid relatedContentId)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.RemoveRelatedContent(relatedContentId);
|
||||
}
|
||||
|
||||
public async Task SetAnnouncementDate(Guid commonPropertiesId, DateTimeOffset value)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.SetAnnouncementDate(value);
|
||||
}
|
||||
public async Task SetEstimatedReleaseDate(Guid commonPropertiesId, DateTimeOffset value)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.SetEstimatedReleaseDate(value);
|
||||
}
|
||||
public async Task SetReleaseDate(Guid commonPropertiesId, DateTimeOffset value)
|
||||
{
|
||||
var commonProperties = await GetCommonProperties(commonPropertiesId);
|
||||
commonProperties.SetReleaseDate(value);
|
||||
}
|
||||
|
||||
private async Task<Domain.Entities.MediaContent.CommonProperties.CommonProperties> GetCommonProperties(string commonPropertiesId)
|
||||
{
|
||||
return await commonPropertiesGateway.GetByMediaContentItemId(commonPropertiesId);
|
||||
}
|
||||
*/
|
||||
}
|
||||
26
Modules.Library.Application/Services/MediaInfoService.cs
Normal file
26
Modules.Library.Application/Services/MediaInfoService.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using Modules.Library.Application.Interfaces;
|
||||
using Modules.Library.Domain.Entities;
|
||||
|
||||
namespace Modules.Library.Application.Services;
|
||||
|
||||
public class MediaInfoService(IMediaInfoGateway mediaInfoGateway)
|
||||
{
|
||||
public async Task Add(IUser user, string url, MediaInfoType type)
|
||||
{
|
||||
if (!user.CanAddMediaInfo()) throw new Exception("AccessDenied");
|
||||
//await mediaInfoGateway.AddAsync(url, type);
|
||||
}
|
||||
|
||||
public async Task Edit(IUser user, Guid id, string url, MediaInfoType type)
|
||||
{
|
||||
if (!user.CanEditMediaInfo()) throw new Exception("AccessDenied");
|
||||
//await domainMediaInfoService.Edit(id, url, type);
|
||||
}
|
||||
|
||||
public async Task Remove(IUser user, Guid id)
|
||||
{
|
||||
if (!user.CanRemoveMediaInfo()) throw new Exception("AccessDenied");
|
||||
//await domainMediaInfoService.Remove(id);
|
||||
}
|
||||
}
|
||||
8
Modules.Library.Core/Domain/Common/IOrderableItem.cs
Normal file
8
Modules.Library.Core/Domain/Common/IOrderableItem.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Modules.Library.Core.Domain.Common;
|
||||
|
||||
public interface IOrderableItem
|
||||
{
|
||||
public uint Order { get; }
|
||||
|
||||
public void SetOrder(uint order);
|
||||
}
|
||||
19
Modules.Library.Core/Domain/Common/MediaInfo.cs
Normal file
19
Modules.Library.Core/Domain/Common/MediaInfo.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace Modules.Library.Core.Domain.Common;
|
||||
public class MediaInfo : Entity
|
||||
{
|
||||
public MediaInfoType Type { get; init; } = MediaInfoType.OtherFile;
|
||||
//public string ContentType { get; set; } = default!;
|
||||
public string Url { get; set; } = default!;
|
||||
public void SetUrl(string value)
|
||||
{
|
||||
Url = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MediaInfoType
|
||||
{
|
||||
Image,
|
||||
Video,
|
||||
Link,
|
||||
OtherFile
|
||||
}
|
||||
7
Modules.Library.Core/Domain/Genre/Genre.cs
Normal file
7
Modules.Library.Core/Domain/Genre/Genre.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Modules.Library.Core.Domain.Genre;
|
||||
|
||||
public class Genre : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; } = default!;
|
||||
}
|
||||
10
Modules.Library.Core/Domain/Language/Language.cs
Normal file
10
Modules.Library.Core/Domain/Language/Language.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Modules.Library.Core.Domain.Language;
|
||||
|
||||
public class Language : Entity
|
||||
{
|
||||
[Required]
|
||||
public string CodeIso2 { get; set; } = default!;
|
||||
[Required]
|
||||
public string Name { get; set; } = default!;
|
||||
public MediaInfo? Icon { get; set; }
|
||||
}
|
||||
@ -0,0 +1,164 @@
|
||||
using System.Transactions;
|
||||
|
||||
namespace Modules.Library.Core.Domain.MediaContent.CommonProperties;
|
||||
|
||||
public class CommonProperties : Entity
|
||||
{
|
||||
private List<NameItem> _names = [];
|
||||
public IReadOnlyCollection<NameItem> Names => _names.AsReadOnly();
|
||||
public MediaInfo? Preview { get; private set; }
|
||||
|
||||
private List<DescriptionItem> _descriptions = [];
|
||||
public IReadOnlyCollection<DescriptionItem> Descriptions => _descriptions.AsReadOnly();
|
||||
|
||||
public List<Genre_Percentage> _genres = [];
|
||||
public IReadOnlyCollection<Genre_Percentage> Genres => _genres.AsReadOnly();
|
||||
|
||||
public List<MediaInfo> _relatedContent = [];
|
||||
public IReadOnlyCollection<MediaInfo> RelatedContent => _relatedContent.AsReadOnly();
|
||||
|
||||
public DateTimeOffset? AnnouncementDate { get; private set; }
|
||||
public DateTimeOffset? EstimatedReleaseDate { get; private set; }
|
||||
public DateTimeOffset? ReleaseDate { get; private set; }
|
||||
|
||||
#region Names
|
||||
public void AddNameOriginal(Guid language)
|
||||
{
|
||||
if (_names.Any(q => q.Type == NameType.Original)) throw new Exception("Original name is already exist.");
|
||||
_names.Add(new NameItem { LanguageId = language, Type = NameType.Original });
|
||||
}
|
||||
public void AddNameOriginalInAnotherLanguage(Guid language, string value)
|
||||
{
|
||||
if (language == _names.Single(q => q.Type == NameType.Original).LanguageId)
|
||||
throw new Exception("Language must not match original name language");
|
||||
|
||||
if (_names.Any(q => q.Type == NameType.OriginalInAnotherLanguage && q.LanguageId == language))
|
||||
throw new Exception("Name in following language is already exist.");
|
||||
|
||||
_names.Add(new NameItem { LanguageId = language, Type = NameType.OriginalInAnotherLanguage });
|
||||
}
|
||||
public void AddNameTranslation(Guid language, string value)
|
||||
{
|
||||
_names.Add(new NameItem { LanguageId = language, Type = NameType.Translation });
|
||||
}
|
||||
public void AddNameAbbreviation(Guid language, string value)
|
||||
{
|
||||
_names.Add(new NameItem { LanguageId = language, Type = NameType.Abbreviation });
|
||||
}
|
||||
|
||||
public void RemoveName(Guid nameId)
|
||||
{
|
||||
var name = GetName(nameId);
|
||||
if (name.Type == NameType.Original) throw new Exception($"Unable to remove original name");
|
||||
_names.Remove(name);
|
||||
}
|
||||
|
||||
public void SetNameValue(Guid nameId, string value)
|
||||
{
|
||||
var name = GetName(nameId);
|
||||
name.SetValue(value);
|
||||
}
|
||||
|
||||
private NameItem GetName(Guid nameId)
|
||||
{
|
||||
return _names.FirstOrDefault(q => q.Id == nameId) ?? throw new Exception($"Name with id [{nameId}] is not found");
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void SetPreview(string url)
|
||||
{
|
||||
Preview ??= new MediaInfo { Type = MediaInfoType.Image };
|
||||
Preview.SetUrl(url);
|
||||
}
|
||||
public void DeletePreview()
|
||||
{
|
||||
Preview = null;
|
||||
}
|
||||
|
||||
#region Descriptions
|
||||
public void AddDescription(Guid language, bool isOriginal)
|
||||
{
|
||||
if (_descriptions.Any(q => q.IsOriginal) && isOriginal) throw new Exception("Could not add one more original description");
|
||||
_descriptions.Add(new DescriptionItem { LanguageId = language, IsOriginal = isOriginal});
|
||||
}
|
||||
|
||||
public void RemoveDescription(Guid descriptionId)
|
||||
{
|
||||
var description = GetDescription(descriptionId);
|
||||
//if (description.IsOriginal) throw new Exception($"Unable to remove original description");
|
||||
_descriptions.Remove(description);
|
||||
}
|
||||
|
||||
public void SetDescriptionValue(Guid descriptionId, string value)
|
||||
{
|
||||
var description = GetDescription(descriptionId);
|
||||
description.SetValue(value);
|
||||
}
|
||||
|
||||
private DescriptionItem GetDescription(Guid descriptionId)
|
||||
{
|
||||
return _descriptions.FirstOrDefault(q => q.Id == descriptionId) ?? throw new Exception($"Description with id [{descriptionId}] is not found");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Genres
|
||||
public void AddGenre(Guid genreId)
|
||||
{
|
||||
_genres.Add(new Genre_Percentage { GenreId = genreId });
|
||||
}
|
||||
|
||||
public void RemoveGenre(Guid genreItemId)
|
||||
{
|
||||
var genre = GetGenreItem(genreItemId);
|
||||
_genres.Remove(genre);
|
||||
}
|
||||
|
||||
public void AddGenreValue(Guid genreItemId, decimal value)
|
||||
{
|
||||
var genre = GetGenreItem(genreItemId);
|
||||
genre.SetValue(value);
|
||||
}
|
||||
private Genre_Percentage GetGenreItem(Guid genreItemId)
|
||||
{
|
||||
return _genres.FirstOrDefault(q => q.Id == genreItemId) ?? throw new Exception($"Genre item with id [{genreItemId}] is not found");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RelatedContents
|
||||
public void AddRelatedContent(MediaInfoType type)
|
||||
{
|
||||
_relatedContent.Add(new MediaInfo { Type = type });
|
||||
}
|
||||
|
||||
public void RemoveRelatedContent(Guid relatedContentId)
|
||||
{
|
||||
var relatedContent = GetRelatedContent(relatedContentId);
|
||||
_relatedContent.Remove(relatedContent);
|
||||
|
||||
}
|
||||
|
||||
public void AddRelatedContent(Guid relatedContentId, string url)
|
||||
{
|
||||
var relatedContent = GetRelatedContent(relatedContentId);
|
||||
relatedContent.SetUrl(url);
|
||||
}
|
||||
|
||||
public MediaInfo GetRelatedContent(Guid relatedContentId)
|
||||
{
|
||||
return _relatedContent.FirstOrDefault(q => q.Id == relatedContentId) ?? throw new Exception($"Related content with id [{relatedContentId}] is not found");
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void SetAnnouncementDate(DateTimeOffset value)
|
||||
{
|
||||
AnnouncementDate = value;
|
||||
}
|
||||
public void SetEstimatedReleaseDate(DateTimeOffset value)
|
||||
{
|
||||
EstimatedReleaseDate = value;
|
||||
}
|
||||
public void SetReleaseDate(DateTimeOffset value)
|
||||
{
|
||||
ReleaseDate = value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.CommonProperties;
|
||||
public class DescriptionItem : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; private set; } = string.Empty;
|
||||
public bool IsOriginal { get; init; }
|
||||
[Required]
|
||||
public Guid LanguageId { get; init; } = default!;
|
||||
|
||||
public void SetValue(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.CommonProperties;
|
||||
|
||||
public class Genre_Percentage : Entity
|
||||
{
|
||||
public decimal Value { get; private set; }
|
||||
[Required]
|
||||
public Guid GenreId { get; init; } = default!;
|
||||
|
||||
public void SetValue(decimal value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.CommonProperties;
|
||||
|
||||
public class NameItem : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; private set; } = string.Empty;
|
||||
public NameType Type { get; init; }
|
||||
[Required]
|
||||
public Guid LanguageId { get; init; } = default!;
|
||||
|
||||
public void SetValue(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum NameType
|
||||
{
|
||||
Original,
|
||||
OriginalInAnotherLanguage,
|
||||
Translation,
|
||||
Abbreviation,
|
||||
}
|
||||
10
Modules.Library.Core/Domain/MediaContent/ICompletable.cs
Normal file
10
Modules.Library.Core/Domain/MediaContent/ICompletable.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent;
|
||||
|
||||
public interface ICompletable
|
||||
{
|
||||
public bool Completed { get; }
|
||||
public TimeSpan ExpirationTime { get; }
|
||||
public void SetCompleted();
|
||||
public void SetNotCompleted();
|
||||
public void SetExpirationTime(TimeSpan value);
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items.Anime;
|
||||
|
||||
public class AnimeEpisode : AnimeItemSingle
|
||||
{
|
||||
public AnimeEpisodeType Type { get; set; } = AnimeEpisodeType.Regilar;
|
||||
public int? Number { get; set; }
|
||||
}
|
||||
|
||||
public enum AnimeEpisodeType
|
||||
{
|
||||
Regilar,
|
||||
FullLength,
|
||||
Ova,
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items.Anime;
|
||||
|
||||
public abstract class AnimeItem : MediaContentItem
|
||||
{
|
||||
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items.Anime;
|
||||
|
||||
public abstract class AnimeItemSingle: AnimeItem, INotContainer
|
||||
{
|
||||
public TimeSpan? Duration { get; set; }
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items.Anime;
|
||||
|
||||
public class AnimeSeason : AnimeItem, IContainer<AnimeItem>
|
||||
{
|
||||
public int? Number { get; set; }
|
||||
public string Director { get; protected set; } = default!;
|
||||
public string OriginCountry { get; protected set; } = default!;
|
||||
|
||||
protected List<AnimeItem> _items = [];
|
||||
public IReadOnlyCollection<AnimeItem> Items => _items.AsReadOnly();
|
||||
|
||||
public void AddEpisode(AnimeEpisode episode)
|
||||
{
|
||||
var lastOrder = _items.OrderByDescending(q => q.Order).FirstOrDefault()?.Order;
|
||||
_items.Add(episode);
|
||||
episode.SetOrder(lastOrder.HasValue ? lastOrder.Value + 1 : 0);
|
||||
CheckIfCompleted();
|
||||
}
|
||||
|
||||
public void AddEpisodeAsVariant(AnimeEpisode episode, uint order)
|
||||
{
|
||||
var lastVariant = _items.OrderByDescending(q => q.Variant).FirstOrDefault(q => q.Order == order)?.Variant ??
|
||||
throw new Exception($"Could not find episode with order [{order}]");
|
||||
|
||||
_items.Add(episode);
|
||||
episode.SetOrder(order);
|
||||
//episode.SetVariant(lastVariant.HasValue ? lastVariant.Value + 1 : 0);
|
||||
episode.SetVariant(lastVariant + 1);
|
||||
CheckIfCompleted();
|
||||
}
|
||||
|
||||
private void CheckIfCompleted()
|
||||
{
|
||||
var itemsQuery = Items.AsQueryable();
|
||||
var unreleasedEpisodesAreExists = itemsQuery
|
||||
.Any(q => !q.CommonProperties.ReleaseDate.HasValue);
|
||||
var lastEpisodeReleaseDate = itemsQuery
|
||||
.OrderByDescending(q => q.CommonProperties.ReleaseDate)
|
||||
.FirstOrDefault()?.CommonProperties.ReleaseDate;
|
||||
//return unreleasedEpisodesAreExists || lastEpisodeReleaseDate - DateTime.UtcNow > expirePeriod;
|
||||
if (unreleasedEpisodesAreExists || lastEpisodeReleaseDate >= (DateTime.UtcNow - ExpirationTime)) SetNotCompleted();
|
||||
else SetCompleted();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items.Anime;
|
||||
|
||||
public class AnimeTitle : TopLevelItem
|
||||
{
|
||||
public void AddSeason()
|
||||
{
|
||||
var season = new AnimeSeason();
|
||||
_items.Add(season);
|
||||
CheckIfCompleted();
|
||||
}
|
||||
|
||||
|
||||
public void AddEpisode(AnimeEpisodeType type, Guid? seasonId = null)
|
||||
{
|
||||
var episode = new AnimeEpisode
|
||||
{
|
||||
Type = type
|
||||
};
|
||||
if (!seasonId.HasValue)
|
||||
{
|
||||
_items.Add(episode);
|
||||
episode.SetOrder(GetItemsLastOrder() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var season = GetSeason(seasonId.Value);
|
||||
season.AddEpisode(episode);
|
||||
}
|
||||
CheckIfCompleted();
|
||||
}
|
||||
|
||||
public void AddEpisodeVariant(AnimeEpisodeType type, uint episodeOrder, Guid? seasonId = null)
|
||||
{
|
||||
var episode = new AnimeEpisode
|
||||
{
|
||||
Type = type
|
||||
};
|
||||
if (!seasonId.HasValue)
|
||||
{
|
||||
var lastVariant = _items.OrderByDescending(q => q.Variant).FirstOrDefault(q => q.Order == episodeOrder)?.Variant ??
|
||||
throw new Exception($"Could not find episode with order [{episodeOrder}]");
|
||||
_items.Add(episode);
|
||||
episode.SetOrder(episodeOrder);
|
||||
episode.SetOrder(lastVariant + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var season = GetSeason(seasonId.Value);
|
||||
season.AddEpisodeAsVariant(episode, episodeOrder);
|
||||
}
|
||||
CheckIfCompleted();
|
||||
}
|
||||
|
||||
private uint GetItemsLastOrder() =>
|
||||
(uint)(_items.OfType<IOrderableItem>()
|
||||
.OrderByDescending(q => q.Order).LastOrDefault()?.Order ?? -1u);
|
||||
private void CheckIfCompleted()
|
||||
{
|
||||
var itemsQuery = Items.AsQueryable();
|
||||
var ucompletedSeasons = itemsQuery
|
||||
.Any(q => !q.CommonProperties.ReleaseDate.HasValue);
|
||||
var lastEpisodeReleaseDate = itemsQuery
|
||||
.OrderByDescending(q => q.CommonProperties.ReleaseDate)
|
||||
.FirstOrDefault()?.CommonProperties.ReleaseDate;
|
||||
if (ucompletedSeasons || lastEpisodeReleaseDate >= (DateTime.UtcNow - ExpirationTime)) SetNotCompleted();
|
||||
else SetCompleted();
|
||||
}
|
||||
|
||||
#region Episode Properties
|
||||
public void SetEpisodeDuration(Guid episodeId, TimeSpan? value, Guid ? seasonId = null)
|
||||
{
|
||||
var episode = GetEpisode(episodeId, seasonId);
|
||||
episode.Duration = value;
|
||||
}
|
||||
|
||||
#region Name
|
||||
public void AddEpisodeName(Guid episodeId, string? value, Guid? seasonId = null)
|
||||
{
|
||||
var episode = GetEpisode(episodeId, seasonId);
|
||||
episode.Duration = value;
|
||||
}
|
||||
|
||||
public void AddName(CommonProperties.CommonProperties commonProperties, Language.Language language, string? value)
|
||||
{
|
||||
var episode = GetEpisode(episodeId, seasonId);
|
||||
episode.Duration = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Description
|
||||
|
||||
#endregion
|
||||
|
||||
#region Genre
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
private AnimeEpisode GetEpisode(Guid episodeId, Guid? seasonId = null)
|
||||
{
|
||||
AnimeEpisode? result = null;
|
||||
if (seasonId.HasValue)
|
||||
{
|
||||
var season = GetSeason(seasonId.Value);
|
||||
result = season.Items.OfType<AnimeEpisode>()
|
||||
.FirstOrDefault(q => q.Id == episodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = _items.OfType<AnimeEpisode>()
|
||||
.FirstOrDefault(q => q.Id == episodeId);
|
||||
}
|
||||
return result ?? throw new Exception($"Could not find episode with id [{episodeId}]");
|
||||
}
|
||||
|
||||
private AnimeSeason GetSeason(Guid seasonId) =>
|
||||
_items.OfType<AnimeSeason>().FirstOrDefault(q => q.Id == seasonId) ??
|
||||
throw new Exception($"Could not find season with id [{seasonId}]");
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items;
|
||||
|
||||
public interface IContainer <T>
|
||||
{
|
||||
public IReadOnlyCollection<T> Items { get; }
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items;
|
||||
|
||||
public interface INotContainer { }
|
||||
|
||||
public interface INotContainer<T>
|
||||
{
|
||||
public IContainer<T> Container { get; }
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Modules.Library.Core.Domain.MediaContent.Items;
|
||||
|
||||
public interface IVariantItem
|
||||
{
|
||||
public uint Variant { get; }
|
||||
|
||||
public void SetVariant(uint variant);
|
||||
}
|
||||
29
Modules.Library.Core/Domain/MediaContent/MediaContentItem.cs
Normal file
29
Modules.Library.Core/Domain/MediaContent/MediaContentItem.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Modules.Library.Core.Domain.MediaContent.Items;
|
||||
|
||||
namespace Modules.Library.Core.Domain.MediaContent;
|
||||
|
||||
public abstract class MediaContentItem : Entity, IOrderableItem, IVariantItem, ICompletable
|
||||
{
|
||||
[Required]
|
||||
public CommonProperties.CommonProperties CommonProperties { get; set; } = default!;
|
||||
|
||||
public uint Order { get; private set; }
|
||||
|
||||
public uint Variant { get; private set; }
|
||||
|
||||
|
||||
|
||||
public bool Completed { get; private set; }
|
||||
|
||||
public TimeSpan ExpirationTime { get; private set; } = TimeSpan.Zero;
|
||||
|
||||
|
||||
public void SetOrder(uint order) => Order = order;
|
||||
|
||||
public void SetVariant(uint variant) => Variant = variant;
|
||||
|
||||
|
||||
public void SetCompleted() => Completed = true;
|
||||
public void SetNotCompleted() => Completed = false;
|
||||
public void SetExpirationTime(TimeSpan value) => ExpirationTime = value;
|
||||
}
|
||||
22
Modules.Library.Core/Domain/MediaContent/TopLevelItem.cs
Normal file
22
Modules.Library.Core/Domain/MediaContent/TopLevelItem.cs
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
namespace Modules.Library.Core.Domain.MediaContent;
|
||||
|
||||
public class TopLevelItem : Entity, IAggregateRoot, ICompletable
|
||||
{
|
||||
[Required]
|
||||
public CommonProperties.CommonProperties CommonProperties { get; set; } = default!;
|
||||
|
||||
protected List<MediaContentItem> _items = [];
|
||||
public IReadOnlyCollection<MediaContentItem> Items => _items.AsReadOnly();
|
||||
|
||||
[Required]
|
||||
public User.User Creator { get; private set; } = default!;
|
||||
|
||||
public bool Completed { get; private set; }
|
||||
|
||||
public TimeSpan ExpirationTime { get; private set; } = TimeSpan.Zero;
|
||||
|
||||
public void SetCompleted() => Completed = true;
|
||||
public void SetNotCompleted() => Completed = false;
|
||||
public void SetExpirationTime(TimeSpan value) => ExpirationTime = value;
|
||||
}
|
||||
12
Modules.Library.Core/Domain/Models/ITopLevelItem.cs
Normal file
12
Modules.Library.Core/Domain/Models/ITopLevelItem.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Modules.Library.Core.Domain.Entities
|
||||
{
|
||||
internal interface ITopLevelItem
|
||||
{
|
||||
}
|
||||
}
|
||||
8
Modules.Library.Core/Domain/User/User.cs
Normal file
8
Modules.Library.Core/Domain/User/User.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using Common.Domain.Abstractions;
|
||||
|
||||
namespace Modules.Library.Core.Domain.User;
|
||||
|
||||
public class User : Entity
|
||||
{
|
||||
|
||||
}
|
||||
4
Modules.Library.Core/GlobalUsings.cs
Normal file
4
Modules.Library.Core/GlobalUsings.cs
Normal file
@ -0,0 +1,4 @@
|
||||
global using Common.Domain.Abstractions;
|
||||
global using Modules.Library.Core.Domain.Common;
|
||||
|
||||
global using System.ComponentModel.DataAnnotations;
|
||||
20
Modules.Library.Core/Modules.Library.Core.csproj
Normal file
20
Modules.Library.Core/Modules.Library.Core.csproj
Normal file
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Domain\Models\MediaContent\" />
|
||||
<Folder Include="EventHandlers\" />
|
||||
<Folder Include="DomainEvents\" />
|
||||
<Folder Include="Repositories\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common.Domain\Common.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,14 @@
|
||||
namespace Modules.Library.Database.Database.Models.Anime;
|
||||
|
||||
public class AnimeEpisode1 : AnimeItem1
|
||||
{
|
||||
public AnimeEpisodeType1 Type { get; set; }
|
||||
public int? Number { get; set; }
|
||||
}
|
||||
|
||||
public enum AnimeEpisodeType1
|
||||
{
|
||||
Regilar,
|
||||
FullLength,
|
||||
Ova,
|
||||
}
|
||||
10
Modules.Library.Database/Database/Models/Anime/AnimeItem1.cs
Normal file
10
Modules.Library.Database/Database/Models/Anime/AnimeItem1.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Modules.Library.Database.Database.Models.Anime;
|
||||
|
||||
public class AnimeItem1 : Entity1
|
||||
{
|
||||
public CommonProperties.CommonProperties1 CommonProperties { get; set; } = default!;
|
||||
public ushort Order { get; set; }
|
||||
public ushort Variant { get; set; }
|
||||
public bool Completed { get; set; }
|
||||
public TimeSpan ExpirationTime { get; set; }
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Modules.Library.Database.Database.Models.Anime;
|
||||
|
||||
public class AnimeSeasonq : AnimeItem1
|
||||
{
|
||||
public List<AnimeEpisode1> Episodes { get; set; } = [];
|
||||
|
||||
public int? Number { get; set; }
|
||||
public string? Director { get; set; }
|
||||
public string? OriginCountry { get; set; }
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Modules.Library.Database.Database.Models.Anime;
|
||||
|
||||
public class AnimeTitle1 : Entity1
|
||||
{
|
||||
public CommonProperties.CommonProperties1 CommonProperties { get; set; } = default!;
|
||||
public List<AnimeItem1> Items { get; set; } = [];
|
||||
public bool Completed { get; set; }
|
||||
public TimeSpan ExpirationTime { get; set; }
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
namespace Modules.Library.Database.Database.Models.CommonProperties;
|
||||
|
||||
public class CommonProperties1 : Entity1
|
||||
{
|
||||
public List<NameItem1> Names { get; set; } = [];
|
||||
public MediaInfo1? Preview { get; set; }
|
||||
|
||||
public List<DescriptionItem1> Descriptions { get; set; } = [];
|
||||
public List<GenreProportionItem1> Genres { get; set; } = [];
|
||||
public List<MediaInfo1> RelatedContent { get; set; } = [];
|
||||
|
||||
public DateTimeOffset? AnnouncementDate { get; set; }
|
||||
public DateTimeOffset? EstimatedReleaseDate { get; set; }
|
||||
public DateTimeOffset? ReleaseDate { get; set; }
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Modules.Library.Database.Database.Models.CommonProperties;
|
||||
|
||||
public class DescriptionItem1 : Entity1
|
||||
{
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public bool IsOriginal { get; set; }
|
||||
public string LanguageId { get; set; } = default!;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Modules.Library.Database.Database.Models.CommonProperties;
|
||||
|
||||
public class GenreProportionItem1 : Entity1
|
||||
{
|
||||
public decimal? Proportion { get; set; }
|
||||
public string GenreId { get; set; } = default!;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
namespace Modules.Library.Database.Database.Models.CommonProperties;
|
||||
|
||||
public class NameItem1 : Entity1
|
||||
{
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public NameType1 Type { get; set; }
|
||||
public string LanguageId { get; set; } = default!;
|
||||
}
|
||||
|
||||
public enum NameType1
|
||||
{
|
||||
Original,
|
||||
OriginalInAnotherLanguage,
|
||||
Translation,
|
||||
Abbreviation,
|
||||
}
|
||||
11
Modules.Library.Database/Database/Models/Entity1.cs
Normal file
11
Modules.Library.Database/Database/Models/Entity1.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace Modules.Library.Database.Database.Models;
|
||||
|
||||
public class Entity1 : Domain.Entities.Entity
|
||||
{
|
||||
public ObjectId ObjectId { get; set; }
|
||||
public override string Id => ObjectId.ToString();
|
||||
|
||||
public void SetDeleted(bool value) => Deleted = value;
|
||||
}
|
||||
6
Modules.Library.Database/Database/Models/Genre/Genre1.cs
Normal file
6
Modules.Library.Database/Database/Models/Genre/Genre1.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Modules.Library.Database.Database.Models.Genre;
|
||||
|
||||
public class Genre1 : Entity1
|
||||
{
|
||||
public string Name { get; set; } = default!;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Modules.Library.Database.Database.Models.Language;
|
||||
|
||||
public class Language1 : Entity1
|
||||
{
|
||||
public string CodeIso2 { get; set; } = default!;
|
||||
public string Name { get; set; } = default!;
|
||||
public Guid? IconId { get; set; }
|
||||
}
|
||||
15
Modules.Library.Database/Database/Models/MediaInfo1.cs
Normal file
15
Modules.Library.Database/Database/Models/MediaInfo1.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Modules.Library.Database.Database.Models;
|
||||
|
||||
public class MediaInfo1 : Entity1
|
||||
{
|
||||
public MediaInfoType1 Type { get; set; } = MediaInfoType1.OtherFile;
|
||||
public string Url { get; set; } = default!;
|
||||
}
|
||||
|
||||
public enum MediaInfoType1
|
||||
{
|
||||
Image,
|
||||
Video,
|
||||
Link,
|
||||
OtherFile
|
||||
}
|
||||
46
Modules.Library.Database/Database/MongoDbContext.cs
Normal file
46
Modules.Library.Database/Database/MongoDbContext.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Modules.Library.Domain.Entities;
|
||||
using Modules.Library.Domain.Entities.Genre;
|
||||
using Modules.Library.Domain.Entities.Language;
|
||||
using Modules.Library.Domain.Entities.MediaContent.CommonProperties;
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
using Modules.Library.Domain.EntityBuilders;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Bson.Serialization.Conventions;
|
||||
using MongoDB.Bson.Serialization.IdGenerators;
|
||||
using MongoDB.Driver;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Modules.Library.Database.Database;
|
||||
|
||||
public class MongoDbContext(IMongoDatabase database)
|
||||
{
|
||||
private bool _initialized;
|
||||
public IMongoCollection<TDocument> GetCollection<TDocument>(string collectionName)
|
||||
{
|
||||
if (!_initialized) throw new Exception(string.Concat(nameof(MongoDbContext), " has not initialized yet"));
|
||||
return database.GetCollection<TDocument>(collectionName);
|
||||
}
|
||||
|
||||
public IMongoCollection<Genre> Genres => GetCollection<Genre>(nameof(Genre));
|
||||
public IMongoCollection<Language> Languages => GetCollection<Language>(nameof(Language));
|
||||
public IMongoCollection<MediaInfo> MediaInfos => GetCollection<MediaInfo>(nameof(MediaInfo));
|
||||
|
||||
private const string _mediaContentItemsCollectionName = "MediaContentItems";
|
||||
public IMongoCollection<AnimeTitle> AnimeTitles => GetCollection<AnimeTitle>(_mediaContentItemsCollectionName);
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
BsonClassMap.RegisterClassMap<AnimeTitle>(q =>
|
||||
{
|
||||
//q.AutoMap();
|
||||
q.MapCreator(q => AnimeTitleBuilder.FromAnimeTitle(q).Build());
|
||||
//q.MapIdMember(c => c.Id).SetIdGenerator(StringObjectIdGenerator.Instance);
|
||||
});
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
//private IntSequence _paymentProviderIdSequence = new("PaymentProviderId");
|
||||
|
||||
//public Task<int> GetNextPaymentProviderId() => _paymentProviderIdSequence.GetNextSequenceValue(context);
|
||||
}
|
||||
18
Modules.Library.Database/Modules.Library.Database.csproj
Normal file
18
Modules.Library.Database/Modules.Library.Database.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.28.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Modules.Library.Application\Modules.Library.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,73 @@
|
||||
using Modules.Library.Application.Gateways;
|
||||
using Modules.Library.Database.Database;
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Modules.Library.Database.Repositories;
|
||||
|
||||
public class AnimeTitleRepository(MongoDbContext context) : RepositoryBase<AnimeTitle>(context), IAnimeTitleGateway
|
||||
{
|
||||
protected override IMongoCollection<AnimeTitle> GetCollections(MongoDbContext context) => context.AnimeTitles;
|
||||
|
||||
protected override async Task<bool> SoftDeleteAsync(AnimeTitle entity)
|
||||
{
|
||||
entity.Delete();
|
||||
return await UpdateAsync(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
public async Task AddAsync(Domain.Entities.MediaContent.Items.Anime.AnimeTitle entity, IUser user) =>
|
||||
await AddAsync(ToDbConverter.Title(entity));
|
||||
|
||||
public Task<bool> AnyWhere(Expression<Func<Domain.Entities.MediaContent.Items.Anime.AnimeTitle, bool>> predicate)
|
||||
{
|
||||
var p = predicate.
|
||||
}
|
||||
|
||||
public Task<bool> DeleteAsync(Domain.Entities.MediaContent.Items.Anime.AnimeTitle entity, IUser user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Domain.Entities.MediaContent.Items.Anime.AnimeTitle> GetByIdAsync(string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Domain.Entities.MediaContent.Items.Anime.AnimeTitle?> GetByIdOrDefaultAsync(string id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Domain.Entities.MediaContent.Items.Anime.AnimeTitle?> GetFirstOrDefaultWhere(Expression<Func<Domain.Entities.MediaContent.Items.Anime.AnimeTitle, bool>> predicate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<Domain.Entities.MediaContent.Items.Anime.AnimeTitle> GetFirstWhere(Expression<Func<Domain.Entities.MediaContent.Items.Anime.AnimeTitle, bool>> predicate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<Domain.Entities.MediaContent.Items.Anime.AnimeTitle>> GetRangeByIdsAsync(List<string> ids)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<Domain.Entities.MediaContent.Items.Anime.AnimeTitle>> GetWhere(Expression<Func<Domain.Entities.MediaContent.Items.Anime.AnimeTitle, bool>> predicate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> UpdateAsync(Domain.Entities.MediaContent.Items.Anime.AnimeTitle entity, IUser user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
Task<List<Domain.Entities.MediaContent.Items.Anime.AnimeTitle>> Application.Gateways.IRepository<Domain.Entities.MediaContent.Items.Anime.AnimeTitle, string>.GetAllAsync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
*/
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
//using Modules.Library.Database.Database.Models;
|
||||
//using Modules.Library.Database.Database.Models.Anime;
|
||||
//using Modules.Library.Database.Database.Models.CommonProperties;
|
||||
//using MongoDB.Bson;
|
||||
|
||||
//namespace Modules.Library.Database.Repositories.Converters;
|
||||
|
||||
//internal static class ToDbConverter
|
||||
//{
|
||||
// internal static AnimeTitle1 Title(Domain.Entities.MediaContent.Items.Anime.AnimeTitle entity)
|
||||
// {
|
||||
// var result = new AnimeTitle1
|
||||
// {
|
||||
// CommonProperties = ToDbConverter.CommonProperties(entity.CommonProperties),
|
||||
// Items = entity.Items.Select(q =>
|
||||
// {
|
||||
// AnimeItem1 result;
|
||||
// if (q is Domain.Entities.MediaContent.Items.Anime.AnimeSeason season) result = Season(season);
|
||||
// else if (q is Domain.Entities.MediaContent.Items.Anime.AnimeEpisode episode) result = Episode(episode);
|
||||
// else throw new NotImplementedException();
|
||||
// return result;
|
||||
// }).ToList(),
|
||||
// Completed = entity.Completed,
|
||||
// ExpirationTime = entity.ExpirationTime,
|
||||
// };
|
||||
// ToDbConverterBase.SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// internal static AnimeSeasonq Season(Domain.Entities.MediaContent.Items.Anime.AnimeSeason entity)
|
||||
// {
|
||||
// var result = new AnimeSeasonq
|
||||
// {
|
||||
// CommonProperties = ToDbConverter.CommonProperties(entity.CommonProperties),
|
||||
// Episodes = entity.Episodes.Select(Episode).ToList(),
|
||||
// Number = entity.Number,
|
||||
// Order = entity.Order,
|
||||
// Variant = entity.Variant,
|
||||
// OriginCountry = entity.OriginCountry,
|
||||
// Completed = entity.Completed,
|
||||
// Director = entity.Director,
|
||||
// ExpirationTime = entity.ExpirationTime,
|
||||
// };
|
||||
// ToDbConverterBase.SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// internal static AnimeEpisode1 Episode(Domain.Entities.MediaContent.Items.Anime.AnimeEpisode entity)
|
||||
// {
|
||||
// var result = new AnimeEpisode1
|
||||
// {
|
||||
// CommonProperties = ToDbConverter.CommonProperties(entity.CommonProperties),
|
||||
// Type = (AnimeEpisodeType1)entity.Type,
|
||||
// Order = entity.Order,
|
||||
// Variant = entity.Variant,
|
||||
// Completed = entity.Completed,
|
||||
// Number = entity.Number,
|
||||
// ExpirationTime = entity.ExpirationTime,
|
||||
// };
|
||||
// ToDbConverterBase.SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// internal static CommonProperties1 CommonProperties(Domain.Entities.MediaContent.CommonProperties.CommonProperties entity)
|
||||
// {
|
||||
// var result = new CommonProperties1
|
||||
// {
|
||||
// Names = entity.Names.Select(NameItem).ToList(),
|
||||
// Preview = entity.Preview != null ? MediaInfo(entity.Preview) : null,
|
||||
// Descriptions = entity.Descriptions.Select(Description).ToList(),
|
||||
// Genres = entity.Genres.Select(Genre).ToList(),
|
||||
// RelatedContent = entity.RelatedContent.Select(MediaInfo).ToList(),
|
||||
// AnnouncementDate = entity.AnnouncementDate,
|
||||
// EstimatedReleaseDate = entity.EstimatedReleaseDate,
|
||||
// ReleaseDate = entity.ReleaseDate,
|
||||
// };
|
||||
// SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// //internal static NameItem NameItem(Domain.Entities.MediaContent.CommonProperties.NameItem entity, bool createNew)
|
||||
// internal static NameItem1 NameItem(Domain.Entities.MediaContent.CommonProperties.NameItem entity)
|
||||
// {
|
||||
// var result = new NameItem1
|
||||
// {
|
||||
// LanguageId = entity.LanguageId,
|
||||
// Type = (NameType1)entity.Type,
|
||||
// Value = entity.Value,
|
||||
// };
|
||||
// SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// //internal static MediaInfo NameItem(Domain.Entities.MediaInfo entity, bool createNew)
|
||||
// internal static MediaInfo1 MediaInfo(Domain.Entities.MediaInfo entity)
|
||||
// {
|
||||
// var result = new MediaInfo1
|
||||
// {
|
||||
// Type = (MediaInfoType1)entity.Type,
|
||||
// Url = entity.Url,
|
||||
// };
|
||||
// SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// internal static DescriptionItem1 Description(Domain.Entities.MediaContent.CommonProperties.DescriptionItem entity)
|
||||
// {
|
||||
// var result = new DescriptionItem1
|
||||
// {
|
||||
// LanguageId = entity.LanguageId,
|
||||
// IsOriginal = entity.IsOriginal,
|
||||
// Value = entity.Value,
|
||||
// };
|
||||
// SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// internal static GenreProportionItem1 Genre(Domain.Entities.MediaContent.CommonProperties.GenreProportionItem entity)
|
||||
// {
|
||||
// var result = new GenreProportionItem1
|
||||
// {
|
||||
// GenreId = entity.GenreId,
|
||||
// Proportion = entity.Proportion,
|
||||
// };
|
||||
// SetId(result, entity.Id);
|
||||
// result.SetDeleted(entity.Deleted);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// internal static void SetId(Entity1 entity, string? domainEntityId = null)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(domainEntityId)) entity.ObjectId = ObjectId.GenerateNewId();
|
||||
// else entity.ObjectId = ObjectId.Parse(domainEntityId);
|
||||
// }
|
||||
|
||||
//}
|
||||
@ -0,0 +1,13 @@
|
||||
//using Modules.Library.Database.Database.Models;
|
||||
//using MongoDB.Bson;
|
||||
|
||||
//namespace Modules.Library.Database.Repositories.Converters;
|
||||
|
||||
//internal static class ToDbConverterBase
|
||||
//{
|
||||
// internal static void SetId(Entity1 entity, string? domainEntityId = null)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(domainEntityId)) entity.ObjectId = ObjectId.GenerateNewId();
|
||||
// else entity.ObjectId = ObjectId.Parse(domainEntityId);
|
||||
// }
|
||||
//}
|
||||
18
Modules.Library.Database/Repositories/GenreRepository.cs
Normal file
18
Modules.Library.Database/Repositories/GenreRepository.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using Modules.Library.Database.Database;
|
||||
using Modules.Library.Domain.Entities.Genre;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Modules.Library.Database.Repositories;
|
||||
|
||||
public class GenreRepository(MongoDbContext context) : RepositoryBase<Genre>(context), IGenreGateway
|
||||
{
|
||||
protected override IMongoCollection<Genre> GetCollections(MongoDbContext context) => context.Genres;
|
||||
/*
|
||||
protected override async Task<bool> SoftDeleteAsync(Genre entity)
|
||||
{
|
||||
//entity.Delete();
|
||||
return await UpdateAsync(entity);
|
||||
}
|
||||
*/
|
||||
}
|
||||
31
Modules.Library.Database/Repositories/IRepository.cs
Normal file
31
Modules.Library.Database/Repositories/IRepository.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using MongoDB.Bson;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using Modules.Library.Domain.Entities;
|
||||
|
||||
namespace Modules.Library.Database.Repositories;
|
||||
|
||||
internal interface IRepository<T> : IRepository<T, Guid> where T : Entity { }
|
||||
|
||||
internal interface IRepository<T, TKey>
|
||||
{
|
||||
Task<List<T>> GetAllAsync();
|
||||
|
||||
Task<T> GetByIdAsync(TKey id);
|
||||
Task<T?> GetByIdOrDefaultAsync(TKey id);
|
||||
|
||||
Task<List<T>> GetRangeByIdsAsync(List<TKey> ids);
|
||||
|
||||
Task<T> GetFirstWhere(Expression<Func<T, bool>> predicate);
|
||||
Task<T?> GetFirstOrDefaultWhere(Expression<Func<T, bool>> predicate);
|
||||
|
||||
Task<List<T>> GetWhere(Expression<Func<T, bool>> predicate);
|
||||
|
||||
Task<bool> AnyWhere(Expression<Func<T, bool>> predicate);
|
||||
|
||||
Task<TKey> AddAsync(T entity);
|
||||
|
||||
Task<bool> UpdateAsync(T entity);
|
||||
|
||||
Task<bool> DeleteAsync(T entity);
|
||||
}
|
||||
11
Modules.Library.Database/Repositories/LanguageRepository.cs
Normal file
11
Modules.Library.Database/Repositories/LanguageRepository.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using Modules.Library.Database.Database;
|
||||
using Modules.Library.Domain.Entities.Language;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Modules.Library.Database.Repositories;
|
||||
|
||||
public class LanguageRepository(MongoDbContext context) : RepositoryBase<Language>(context), ILanguageGateway
|
||||
{
|
||||
protected override IMongoCollection<Language> GetCollections(MongoDbContext context) => context.Languages;
|
||||
}
|
||||
11
Modules.Library.Database/Repositories/MediaInfoRepository.cs
Normal file
11
Modules.Library.Database/Repositories/MediaInfoRepository.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Modules.Library.Database.Database;
|
||||
using Modules.Library.Domain.Entities;
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Modules.Library.Database.Repositories;
|
||||
|
||||
public class MediaInfoRepository(MongoDbContext context) : RepositoryBase<MediaInfo>(context), IMediaInfoGateway
|
||||
{
|
||||
protected override IMongoCollection<MediaInfo> GetCollections(MongoDbContext context) => context.MediaInfos;
|
||||
}
|
||||
58
Modules.Library.Database/Repositories/RepositoryBase.cs
Normal file
58
Modules.Library.Database/Repositories/RepositoryBase.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using MongoDB.Driver;
|
||||
using System.Linq.Expressions;
|
||||
using Modules.Library.Database.Database;
|
||||
using Modules.Library.Domain.Entities;
|
||||
|
||||
namespace Modules.Library.Database.Repositories;
|
||||
|
||||
public abstract class RepositoryBase<T>(MongoDbContext context) : IRepository<T> where T : Entity
|
||||
{
|
||||
protected abstract IMongoCollection<T> GetCollections(MongoDbContext context);
|
||||
protected bool _useSoftDelete = true;
|
||||
|
||||
public async Task<Guid> AddAsync(T entity)
|
||||
{
|
||||
await GetCollections(context).InsertOneAsync(entity);
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(T entity)
|
||||
{
|
||||
if (!_useSoftDelete)
|
||||
{
|
||||
var document = await GetCollections(context).FindOneAndDeleteAsync(q => q.Id == entity.Id);
|
||||
return document != null;
|
||||
}
|
||||
else return await SoftDeleteAsync(entity);
|
||||
}
|
||||
|
||||
protected virtual Task<bool> SoftDeleteAsync(T entity) => throw new NotImplementedException();
|
||||
|
||||
public async Task<List<T>> GetAllAsync() => await GetCollections(context).Find("{}").ToListAsync();
|
||||
|
||||
public async Task<T> GetByIdAsync(Guid id) => await GetCollections(context).Find(q => q.Id == id).SingleAsync();
|
||||
|
||||
public async Task<T?> GetByIdOrDefaultAsync(Guid id) => await GetCollections(context).Find(q => q.Id == id).SingleOrDefaultAsync();
|
||||
|
||||
public async Task<T> GetFirstWhere(Expression<Func<T, bool>> predicate) =>
|
||||
await GetCollections(context).Find(predicate).SingleAsync();
|
||||
|
||||
public async Task<T?> GetFirstOrDefaultWhere(Expression<Func<T, bool>> predicate) =>
|
||||
await GetCollections(context).Find(predicate).SingleOrDefaultAsync();
|
||||
|
||||
public async Task<List<T>> GetRangeByIdsAsync(List<Guid> ids) =>
|
||||
await GetCollections(context).Find(q => ids.Contains(q.Id)).ToListAsync();
|
||||
|
||||
public async Task<List<T>> GetWhere(Expression<Func<T, bool>> predicate) =>
|
||||
await GetCollections(context).Find(predicate).ToListAsync();
|
||||
|
||||
public async Task<bool> AnyWhere(Expression<Func<T, bool>> predicate) =>
|
||||
await GetCollections(context).Find(predicate).AnyAsync();
|
||||
|
||||
public async Task<bool> UpdateAsync(T entity)
|
||||
{
|
||||
//var document = await _context.PaymentCollections.FindOneAndReplaceAsync(q => q.Id == entity.Id, entity, new () { ReturnDocument = ReturnDocument.After });
|
||||
var document = await GetCollections(context).FindOneAndReplaceAsync(q => q.Id == entity.Id, entity);
|
||||
return document != null;
|
||||
}
|
||||
}
|
||||
59
Modules.Library.Database/ServiceCollectionExtensions.cs
Normal file
59
Modules.Library.Database/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Modules.Library.Application.Gateways;
|
||||
using Modules.Library.Database.Database;
|
||||
using Modules.Library.Database.Repositories;
|
||||
using Modules.Library.Domain.Gateways;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Modules.Library.Database;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddDatabase(this IServiceCollection services, string connectionString)
|
||||
{
|
||||
AddMongoDb(services, connectionString);
|
||||
services.AddScoped<MongoDbContext>(q =>
|
||||
{
|
||||
var context = new MongoDbContext(q.GetService<IMongoDatabase>());
|
||||
context.Initialize();
|
||||
return context;
|
||||
});
|
||||
AddRepositories(services);
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDatabase(this IServiceCollection services, string connectionString, string? databaseName)
|
||||
{
|
||||
AddMongoDb(services, connectionString, databaseName);
|
||||
//services.AddScoped<MongoDbContext>();
|
||||
services.AddScoped<MongoDbContext>(q =>
|
||||
{
|
||||
var context = new MongoDbContext(q.GetService<IMongoDatabase>());
|
||||
context.Initialize();
|
||||
return context;
|
||||
});
|
||||
AddRepositories(services);
|
||||
return services;
|
||||
}
|
||||
|
||||
private static void AddRepositories(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAnimeTitleGateway, AnimeTitleRepository>();
|
||||
services.AddScoped<IGenreGateway, GenreRepository>();
|
||||
services.AddScoped<ILanguageGateway, LanguageRepository>();
|
||||
services.AddScoped<IMediaInfoGateway, MediaInfoRepository>();
|
||||
}
|
||||
|
||||
private static void AddMongoDb(this IServiceCollection services, string? connectionString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString));
|
||||
services.AddSingleton(new MongoClient(connectionString));
|
||||
}
|
||||
|
||||
private static void AddMongoDb(IServiceCollection services, string? connectionString, string? databaseName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString));
|
||||
if (string.IsNullOrWhiteSpace(databaseName)) throw new ArgumentNullException(nameof(databaseName));
|
||||
services.AddSingleton(new MongoClient(connectionString).GetDatabase(databaseName));
|
||||
}
|
||||
}
|
||||
53
Modules.Library.Domain/Entities/Entity.cs
Normal file
53
Modules.Library.Domain/Entities/Entity.cs
Normal file
@ -0,0 +1,53 @@
|
||||
namespace Modules.Library.Domain.Entities;
|
||||
|
||||
public abstract class Entity
|
||||
{
|
||||
private int? _requestedHashCode;
|
||||
|
||||
public virtual Guid Id { get; protected set; } = Guid.NewGuid();
|
||||
|
||||
public virtual bool Deleted { get; protected set; }
|
||||
|
||||
internal virtual void SetDeleted() => Deleted = true;
|
||||
|
||||
public bool IsTransient() => Id == default;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj == null || obj is not Entity)
|
||||
return false;
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
|
||||
if (GetType() != obj.GetType())
|
||||
return false;
|
||||
|
||||
Entity item = (Entity)obj;
|
||||
|
||||
if (item.IsTransient() || IsTransient())
|
||||
return false;
|
||||
else
|
||||
return item.Id == Id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (!IsTransient())
|
||||
{
|
||||
if (!_requestedHashCode.HasValue)
|
||||
_requestedHashCode = Id.GetHashCode() ^ 31; // XOR for random distribution (http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx)
|
||||
return _requestedHashCode.Value;
|
||||
}
|
||||
else
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(Entity? left, Entity? right) =>
|
||||
Equals(left, null) ? Equals(right, null) : left.Equals(right);
|
||||
|
||||
public static bool operator !=(Entity? left, Entity? right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
11
Modules.Library.Domain/Entities/Genre/Genre.cs
Normal file
11
Modules.Library.Domain/Entities/Genre/Genre.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Modules.Library.Domain.Entities.Genre;
|
||||
|
||||
public class Genre : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; private set; } = default!;
|
||||
|
||||
//private Genre() { }
|
||||
internal Genre(string name) { }
|
||||
public void SetName(string name) => Name = name;
|
||||
}
|
||||
8
Modules.Library.Domain/Entities/IOrderableItem.cs
Normal file
8
Modules.Library.Domain/Entities/IOrderableItem.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Modules.Library.Domain.Entities;
|
||||
|
||||
public interface IOrderableItem
|
||||
{
|
||||
public ushort Order { get; }
|
||||
|
||||
public void SetOrder(ushort order);
|
||||
}
|
||||
21
Modules.Library.Domain/Entities/Language/Language.cs
Normal file
21
Modules.Library.Domain/Entities/Language/Language.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace Modules.Library.Domain.Entities.Language;
|
||||
|
||||
public class Language : Entity
|
||||
{
|
||||
[Required]
|
||||
public string CodeIso2 { get; private set; } = default!;
|
||||
[Required]
|
||||
public string Name { get; private set; } = default!;
|
||||
public Guid? IconId { get; private set; }
|
||||
|
||||
private Language() { }
|
||||
internal Language(string codeIso2, string name, Guid? iconId)
|
||||
{
|
||||
CodeIso2 = codeIso2;
|
||||
Name = name;
|
||||
IconId = iconId;
|
||||
}
|
||||
|
||||
internal void SetName(string name) => Name = name;
|
||||
internal void SetIcon(Guid? iconId) => IconId = iconId;
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.CommonProperties;
|
||||
|
||||
public class CommonProperties : Entity
|
||||
{
|
||||
public List<NameItem> Names { get; set; } = [];
|
||||
public MediaInfo? Preview { get; set; }
|
||||
|
||||
public List<DescriptionItem> Descriptions { get; set; } = [];
|
||||
public List<GenreProportionItem> Genres { get; set; } = [];
|
||||
public List<MediaInfo> RelatedContent { get; set; } = [];
|
||||
|
||||
public DateTimeOffset? AnnouncementDate { get; internal set; }
|
||||
public DateTimeOffset? EstimatedReleaseDate { get; internal set; }
|
||||
public DateTimeOffset? ReleaseDate { get; internal set; }
|
||||
|
||||
internal CommonProperties() { }
|
||||
|
||||
internal CommonProperties(string nameOriginal, Guid nameOriginalLanguageId)
|
||||
{
|
||||
Names.Add(new NameItem(nameOriginalLanguageId, NameType.Original, nameOriginal));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.CommonProperties;
|
||||
public class DescriptionItem : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; internal set; } = string.Empty;
|
||||
public bool IsOriginal { get; init; }
|
||||
[Required]
|
||||
public Guid LanguageId { get; init; } = default!;
|
||||
|
||||
internal DescriptionItem() { }
|
||||
|
||||
internal DescriptionItem(Guid languageId, bool isOriginal, string value)
|
||||
{
|
||||
LanguageId = languageId;
|
||||
IsOriginal = isOriginal;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.CommonProperties;
|
||||
|
||||
public class GenreProportionItem : Entity
|
||||
{
|
||||
public decimal? Proportion { get; internal set; }
|
||||
[Required]
|
||||
public Guid GenreId { get; init; } = default!;
|
||||
|
||||
private GenreProportionItem() { }
|
||||
|
||||
internal GenreProportionItem(Guid genreId, decimal? proportion)
|
||||
{
|
||||
GenreId = genreId;
|
||||
Proportion = proportion;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.CommonProperties;
|
||||
|
||||
public class NameItem : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; set; } = string.Empty;
|
||||
public NameType Type { get; set; }
|
||||
[Required]
|
||||
public Guid LanguageId { get; set; } = default!;
|
||||
|
||||
private NameItem() { }
|
||||
|
||||
internal NameItem(Guid languageId, NameType type, string value)
|
||||
{
|
||||
LanguageId = languageId;
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public void SetValue(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum NameType
|
||||
{
|
||||
Original,
|
||||
OriginalInAnotherLanguage,
|
||||
Translation,
|
||||
Abbreviation,
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.CommonProperties.ShadowEntities;
|
||||
|
||||
internal class CommonPropertiesShadow
|
||||
{
|
||||
internal List<NameItem> names = [];
|
||||
internal MediaInfo? preview = null;
|
||||
internal List<DescriptionItem> descriptions = [];
|
||||
internal List<GenreProportionItem> genres = [];
|
||||
internal List<MediaInfo> relatedContent = [];
|
||||
internal DateTimeOffset? announcementDate = null;
|
||||
internal DateTimeOffset? estimatedReleaseDate = null;
|
||||
internal DateTimeOffset? releaseDate = null;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
public class AnimeEpisode : AnimeItemSingle
|
||||
{
|
||||
public AnimeEpisodeType Type { get; set; }
|
||||
public int? Number { get; set; }
|
||||
|
||||
private AnimeEpisode() : base() { }
|
||||
|
||||
internal AnimeEpisode(string nameOriginal, Guid nameOriginalLanguageId, AnimeEpisodeType type) :
|
||||
base(nameOriginal, nameOriginalLanguageId) { Type = type; }
|
||||
}
|
||||
|
||||
public enum AnimeEpisodeType
|
||||
{
|
||||
Regilar,
|
||||
FullLength,
|
||||
Ova,
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
public abstract class AnimeItem : Entity
|
||||
{
|
||||
public CommonProperties.CommonProperties CommonProperties { get; set; } = new();
|
||||
public ushort Order { get; set; }
|
||||
public ushort Variant { get; set; }
|
||||
public bool Completed { get; set; }
|
||||
public TimeSpan ExpirationTime { get; set; }
|
||||
|
||||
internal AnimeItem() { }
|
||||
|
||||
internal AnimeItem(string nameOriginal, Guid nameOriginalLanguageId)
|
||||
{
|
||||
CommonProperties = new(nameOriginal, nameOriginalLanguageId);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
public abstract class AnimeItemSingle : AnimeItem
|
||||
{
|
||||
public TimeSpan? Duration { get; set; }
|
||||
|
||||
internal AnimeItemSingle() { }
|
||||
internal AnimeItemSingle(string nameOriginal, Guid nameOriginalLanguageId) : base(nameOriginal, nameOriginalLanguageId) { }
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
public class AnimeSeason : AnimeItem
|
||||
{
|
||||
public List<AnimeEpisode> Episodes { get; set; } = [];
|
||||
|
||||
public int? Number { get; set; }
|
||||
public string? Director { get; set; }
|
||||
public string? OriginCountry { get; set; }
|
||||
|
||||
internal AnimeSeason(string nameOriginal, Guid nameOriginalLanguageId) : base(nameOriginal, nameOriginalLanguageId) { }
|
||||
}
|
||||
@ -0,0 +1,177 @@
|
||||
using Modules.Library.Domain.Entities.MediaContent.CommonProperties;
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
public class AnimeTitle : Entity
|
||||
{
|
||||
public CommonProperties.CommonProperties CommonProperties { get; private set; } = new();
|
||||
|
||||
/*
|
||||
private List<AnimeItem> _items = [];
|
||||
public IReadOnlyList<AnimeItem> Items => _items.AsReadOnly();
|
||||
*/
|
||||
public List<AnimeItem> Items { get; set; } = [];
|
||||
|
||||
public bool Completed { get; internal set; }
|
||||
|
||||
public TimeSpan ExpirationTime { get; set; }
|
||||
|
||||
private AnimeTitle() { }
|
||||
|
||||
internal AnimeTitle(string nameOriginal, Guid nameOriginalLanguageId)
|
||||
{
|
||||
CommonProperties.Names.Add(new NameItem(nameOriginalLanguageId, NameType.Original, nameOriginal));
|
||||
}
|
||||
|
||||
private AnimeItem ConvertToAnimeItem(AnimeItemShadow shadow)
|
||||
{
|
||||
if (shadow is AnimeEpisodeShadow episode) return new AnimeEpisode(this, null, episode);
|
||||
else if (shadow is AnimeSeasonShadow season) return new AnimeSeason(this, season);
|
||||
else throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public void AddEpisode(AnimeEpisodeType episodeType)
|
||||
{
|
||||
var episode = new AnimeEpisodeShadow
|
||||
{
|
||||
type = episodeType,
|
||||
order = AnimeItem.GetNewOrder(_shadow.items),
|
||||
number = _shadow.items.OfType<AnimeEpisodeShadow>().Select(q => q.number).DefaultIfEmpty(-1).Max() + 1,
|
||||
};
|
||||
_shadow.items.Add(episode);
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
public void AddEpisodeAsVariant(AnimeEpisodeType episodeType, ushort order)
|
||||
{
|
||||
if (order < 0 || order > _shadow.items.Select(q => q.order).DefaultIfEmpty<ushort>(0).Max())
|
||||
throw new ArgumentOutOfRangeException(nameof(order));
|
||||
|
||||
var episode = new AnimeEpisodeShadow
|
||||
{
|
||||
type = episodeType,
|
||||
order = order,
|
||||
variant = AnimeItem.GetNewVariant(_shadow.items, order),
|
||||
number = _shadow.items.OfType<AnimeEpisodeShadow>().FirstOrDefault(q => q.order == order)?.number,
|
||||
};
|
||||
_shadow.items.Add(episode);
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
public void RemoveEpisode(AnimeEpisode episode)
|
||||
{
|
||||
episode.SetDeleted();
|
||||
AnimeItem.Sort(_shadow.items.Where(q => !q.deleted));
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
internal void SetEpisodeOrder(ushort currentOrder, ushort newOrder) =>
|
||||
AnimeItem.SetItemOrder(_shadow.items, currentOrder, newOrder);
|
||||
|
||||
internal void SetEpisodeVariant(ushort order, ushort currentVariant, ushort newVariant) =>
|
||||
AnimeItem.SetItemVariant(_shadow.items.OfType<AnimeEpisodeShadow>()
|
||||
.Where(q => q.order == order), currentVariant, newVariant);
|
||||
|
||||
internal void SetEpisodeCompleted(ushort order, ushort variant, bool value = true) =>
|
||||
SetCompleted<AnimeEpisodeShadow>(order, variant, value);
|
||||
|
||||
internal void SetEpisodeExpirationTime(ushort order, ushort variant, TimeSpan value) =>
|
||||
SetExpirationTime<AnimeEpisodeShadow>(order, variant, value);
|
||||
|
||||
|
||||
|
||||
public void AddSeason()
|
||||
{
|
||||
var episode = new AnimeSeasonShadow
|
||||
{
|
||||
order = AnimeItem.GetNewOrder(_shadow.items),
|
||||
number = _shadow.items.OfType<AnimeSeasonShadow>().Select(q => q.number).DefaultIfEmpty(-1).Max() + 1,
|
||||
};
|
||||
_shadow.items.Add(episode);
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
/*
|
||||
public void AddSeasonAsVariant(AnimeEpisodeType episodeType, ushort order)
|
||||
{
|
||||
if (order < 0 || order > _shadow.items.Select(q => q.order).DefaultIfEmpty<ushort>(0).Max())
|
||||
throw new ArgumentOutOfRangeException(nameof(order));
|
||||
|
||||
var episode = new AnimeEpisodeShadow
|
||||
{
|
||||
type = episodeType,
|
||||
order = order,
|
||||
variant = AnimeItem.GetNewVariant(_shadow.items, order),
|
||||
number = _shadow.items.OfType<AnimeEpisodeShadow>().FirstOrDefault(q => q.order == order)?.number,
|
||||
};
|
||||
_shadow.items.Add(episode);
|
||||
CheckIsCompleted();
|
||||
}
|
||||
*/
|
||||
|
||||
public void RemoveSeason(AnimeSeason season)
|
||||
{
|
||||
season.SetDeleted();
|
||||
AnimeItem.Sort(_shadow.items.Where(q => !q.deleted));
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
internal void SetSeasonCompleted(ushort order, ushort variant, bool value = true) =>
|
||||
SetCompleted<AnimeSeasonShadow>(order, variant, value);
|
||||
|
||||
internal void SetSeasonExpirationTime(ushort order, ushort variant, TimeSpan value) =>
|
||||
SetExpirationTime<AnimeSeasonShadow>(order, variant, value);
|
||||
|
||||
private void SetCompleted<T>(ushort order, ushort variant, bool value = true) where T : AnimeItemShadow
|
||||
{
|
||||
var item = GetItem<T>(order, variant);
|
||||
item.completed = value;
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
private void SetExpirationTime<T>(ushort order, ushort variant, TimeSpan value) where T : AnimeItemShadow
|
||||
{
|
||||
var item = GetItem<T>(order, variant);
|
||||
item.expirationTime = value;
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
private T GetItem<T>(ushort order, ushort variant) where T : AnimeItemShadow
|
||||
{
|
||||
var type = typeof(T);
|
||||
var itemName = string.Empty;
|
||||
if (type == typeof(AnimeEpisodeShadow)) itemName = "Episode";
|
||||
else if (type == typeof(AnimeSeason)) itemName = "Season";
|
||||
else throw new NotImplementedException();
|
||||
return _shadow.items.OfType<T>()
|
||||
.FirstOrDefault(q => q.order == order && q.variant == variant) ??
|
||||
throw new Exception(string.Concat(itemName, " not found"));
|
||||
}
|
||||
|
||||
public void SetCompleted() => _shadow.completed = true;
|
||||
|
||||
public void SetNotCompleted() => _shadow.completed = false;
|
||||
|
||||
public void SetExpirationTime(TimeSpan value)
|
||||
{
|
||||
_shadow.expirationTime = value;
|
||||
CheckIsCompleted();
|
||||
}
|
||||
|
||||
public void Delete() => SetDeleted();
|
||||
internal override void SetDeleted() => _shadow.deleted = true;
|
||||
|
||||
internal void CheckIsCompleted()
|
||||
{
|
||||
var itemsQuery = Items.AsQueryable();
|
||||
var ucompletedSeasons = itemsQuery
|
||||
.Any(q => !q.CommonProperties.ReleaseDate.HasValue);
|
||||
var lastEpisodeReleaseDate = itemsQuery
|
||||
.OrderByDescending(q => q.CommonProperties.ReleaseDate)
|
||||
.FirstOrDefault()?.CommonProperties.ReleaseDate;
|
||||
if (ucompletedSeasons || lastEpisodeReleaseDate >= DateTime.UtcNow - ExpirationTime) SetNotCompleted();
|
||||
else SetCompleted();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
using Modules.Library.Domain.Entities.MediaContent.CommonProperties.ShadowEntities;
|
||||
using Modules.Library.Domain.Entities.MediaContent.Items.Anime;
|
||||
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
internal class AnimeEpisodeShadow : AnimeItemSingleShadow
|
||||
{
|
||||
internal AnimeEpisodeType type = AnimeEpisodeType.Regilar;
|
||||
internal int? number = null;
|
||||
|
||||
internal AnimeEpisodeShadow() : base(new CommonPropertiesShadow()) { }
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Modules.Library.Domain.Entities.MediaContent.CommonProperties.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
internal abstract class AnimeItemShadow(CommonPropertiesShadow commonProperties)
|
||||
{
|
||||
internal string id = Guid.NewGuid().ToString();
|
||||
internal CommonPropertiesShadow commonProperties = commonProperties;
|
||||
internal ushort order = 0;
|
||||
internal ushort variant = 0;
|
||||
internal bool completed = false;
|
||||
internal TimeSpan expirationTime = TimeSpan.Zero;
|
||||
internal bool deleted = false;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
using Modules.Library.Domain.Entities.MediaContent.CommonProperties.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
internal abstract class AnimeItemSingleShadow(CommonPropertiesShadow commonProperties) : AnimeItemShadow(commonProperties)
|
||||
{
|
||||
internal TimeSpan? duration = null;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
using Modules.Library.Domain.Entities.MediaContent.CommonProperties.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
internal class AnimeSeasonShadow : AnimeItemShadow
|
||||
{
|
||||
internal List<AnimeEpisodeShadow> episodes => [];
|
||||
internal int? number = null;
|
||||
internal string? director = null;
|
||||
internal string? originCountry = null;
|
||||
|
||||
internal AnimeSeasonShadow() : base(new CommonPropertiesShadow())
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
using Modules.Library.Domain.Entities.MediaContent.CommonProperties.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.Entities.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
internal class AnimeTitleShadow(CommonPropertiesShadow commonPropertiesShadow)
|
||||
{
|
||||
internal string id = Guid.NewGuid().ToString();
|
||||
internal CommonPropertiesShadow commonProperties = commonPropertiesShadow;
|
||||
internal List<AnimeItemShadow> items = [];
|
||||
internal bool completed = false;
|
||||
internal TimeSpan expirationTime = TimeSpan.Zero;
|
||||
internal bool deleted = false;
|
||||
}
|
||||
15
Modules.Library.Domain/Entities/MediaInfo.cs
Normal file
15
Modules.Library.Domain/Entities/MediaInfo.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Modules.Library.Domain.Entities;
|
||||
public class MediaInfo : Entity
|
||||
{
|
||||
public MediaInfoType Type { get; internal set; } = MediaInfoType.OtherFile;
|
||||
//public string ContentType { get; set; } = default!;
|
||||
public string Url { get; internal set; } = default!;
|
||||
|
||||
internal MediaInfo() { }
|
||||
|
||||
internal MediaInfo(string url, MediaInfoType type)
|
||||
{
|
||||
Url = url;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
9
Modules.Library.Domain/Entities/MediaInfoType.cs
Normal file
9
Modules.Library.Domain/Entities/MediaInfoType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Modules.Library.Domain.Entities;
|
||||
|
||||
public enum MediaInfoType
|
||||
{
|
||||
Image,
|
||||
Video,
|
||||
Link,
|
||||
OtherFile
|
||||
}
|
||||
53
Modules.Library.Domain/EntitiesV0/Entity.cs
Normal file
53
Modules.Library.Domain/EntitiesV0/Entity.cs
Normal file
@ -0,0 +1,53 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0;
|
||||
|
||||
public abstract class Entity
|
||||
{
|
||||
private int? _requestedHashCode;
|
||||
|
||||
public virtual string Id { get; protected set; } = Guid.NewGuid().ToString();
|
||||
|
||||
public virtual bool Deleted { get; protected set; }
|
||||
|
||||
internal virtual void SetDeleted() => Deleted = true;
|
||||
|
||||
public bool IsTransient() => Id == default;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj == null || obj is not Entity)
|
||||
return false;
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
|
||||
if (GetType() != obj.GetType())
|
||||
return false;
|
||||
|
||||
Entity item = (Entity)obj;
|
||||
|
||||
if (item.IsTransient() || IsTransient())
|
||||
return false;
|
||||
else
|
||||
return item.Id == Id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (!IsTransient())
|
||||
{
|
||||
if (!_requestedHashCode.HasValue)
|
||||
_requestedHashCode = Id.GetHashCode() ^ 31; // XOR for random distribution (http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx)
|
||||
return _requestedHashCode.Value;
|
||||
}
|
||||
else
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(Entity? left, Entity? right) =>
|
||||
Equals(left, null) ? Equals(right, null) : left.Equals(right);
|
||||
|
||||
public static bool operator !=(Entity? left, Entity? right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
12
Modules.Library.Domain/EntitiesV0/Genre/Genre.cs
Normal file
12
Modules.Library.Domain/EntitiesV0/Genre/Genre.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0.Genre;
|
||||
|
||||
public class Genre : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; private set; } = default!;
|
||||
private Genre() { }
|
||||
|
||||
public static Genre New(string name) => new(){ Name = name };
|
||||
|
||||
public void SetName(string name) => Name = name;
|
||||
}
|
||||
8
Modules.Library.Domain/EntitiesV0/IOrderableItem.cs
Normal file
8
Modules.Library.Domain/EntitiesV0/IOrderableItem.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0;
|
||||
|
||||
public interface IOrderableItem
|
||||
{
|
||||
public ushort Order { get; }
|
||||
|
||||
public void SetOrder(ushort order);
|
||||
}
|
||||
18
Modules.Library.Domain/EntitiesV0/Language/Language.cs
Normal file
18
Modules.Library.Domain/EntitiesV0/Language/Language.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0.Language;
|
||||
|
||||
public class Language : Entity
|
||||
{
|
||||
[Required]
|
||||
public string CodeIso2 { get; private set; } = default!;
|
||||
[Required]
|
||||
public string Name { get; private set; } = default!;
|
||||
public Guid? IconId { get; private set; }
|
||||
|
||||
private Language() { }
|
||||
|
||||
internal static Language New(string codeIso2, string name, Guid? iconId) =>
|
||||
new() { CodeIso2 = codeIso2, Name = name, IconId = iconId };
|
||||
|
||||
internal void SetName(string name) => Name = name;
|
||||
internal void SetIcon(Guid? iconId) => IconId = iconId;
|
||||
}
|
||||
@ -0,0 +1,240 @@
|
||||
using Modules.Library.Domain.EntitiesV0.MediaContent.CommonProperties.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.CommonProperties;
|
||||
|
||||
public class CommonProperties : Entity
|
||||
{
|
||||
private readonly CommonPropertiesShadow _shadow;
|
||||
public IReadOnlyCollection<NameItem> Names => _shadow.names.AsReadOnly();
|
||||
public MediaInfo? Preview => _shadow.preview;
|
||||
|
||||
public IReadOnlyCollection<DescriptionItem> Descriptions => _shadow.descriptions.AsReadOnly();
|
||||
public IReadOnlyCollection<GenreProportionItem> Genres => _shadow.genres.AsReadOnly();
|
||||
public IReadOnlyCollection<MediaInfo> RelatedContent => _shadow.relatedContent.AsReadOnly();
|
||||
|
||||
public DateTimeOffset? AnnouncementDate => _shadow.announcementDate;
|
||||
internal Action? AfterAnnouncementDateSet;
|
||||
public DateTimeOffset? EstimatedReleaseDate => _shadow.estimatedReleaseDate;
|
||||
internal Action? AfterEstimatedReleaseDateSet;
|
||||
public DateTimeOffset? ReleaseDate => _shadow.releaseDate;
|
||||
internal Action? AfterReleaseDateSet;
|
||||
|
||||
public override bool Deleted => false;
|
||||
|
||||
internal CommonProperties(CommonPropertiesShadow shadow, Action? afterAnnouncementDateSet, Action? afterEstimatedReleaseDateSet, Action? afterReleaseDateSet)
|
||||
{
|
||||
_shadow = shadow;
|
||||
AfterAnnouncementDateSet = afterAnnouncementDateSet;
|
||||
AfterEstimatedReleaseDateSet = afterEstimatedReleaseDateSet;
|
||||
AfterReleaseDateSet = afterReleaseDateSet;
|
||||
}
|
||||
|
||||
|
||||
#region Name
|
||||
internal void AddNameOriginal(string languageId, string value)
|
||||
{
|
||||
if (_shadow.names.Any(q => q.Type == NameType.Original)) throw new Exception("Original name is already exist.");
|
||||
var name = new NameItem(languageId, NameType.Original, value);
|
||||
AddName(name, value);
|
||||
}
|
||||
|
||||
internal void AddNameOriginalInAnotherLanguage(string languageId, string value)
|
||||
{
|
||||
if (languageId == _shadow.names.Single(q => q.Type == NameType.Original).LanguageId)
|
||||
throw new Exception("Language must not match original name language");
|
||||
|
||||
if (_shadow.names.Any(q => q.Type == NameType.OriginalInAnotherLanguage && q.LanguageId == languageId))
|
||||
throw new Exception("Name in following language is already exist.");
|
||||
|
||||
var name = new NameItem(languageId, NameType.OriginalInAnotherLanguage);
|
||||
AddName(name, value);
|
||||
}
|
||||
internal void AddNameTranslation(string languageId, string value)
|
||||
{
|
||||
var name = new NameItem(languageId, NameType.Translation);
|
||||
AddName(name, value);
|
||||
}
|
||||
internal void AddNameAbbreviation(string languageId, string value)
|
||||
{
|
||||
var name = new NameItem(languageId, NameType.Abbreviation);
|
||||
AddName(name, value);
|
||||
}
|
||||
|
||||
private void AddName(NameItem nameItem, string value)
|
||||
{
|
||||
_shadow.names.Add(nameItem);
|
||||
SetNameValue(nameItem, value);
|
||||
}
|
||||
|
||||
internal void RemoveName(string nameId)
|
||||
{
|
||||
var name = GetName(nameId);
|
||||
if (name.Type == NameType.Original) throw new Exception($"Unable to remove original name");
|
||||
name.SetDeleted();
|
||||
//_names.Remove(name);
|
||||
}
|
||||
|
||||
internal void SetNameValue(string nameId, string value) => SetNameValue(GetName(nameId), value);
|
||||
|
||||
private void SetNameValue(NameItem nameItem, string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value));
|
||||
if (nameItem.Type != NameType.Original && _shadow.names.Any(q => q.LanguageId == nameItem.LanguageId && q.Value == value))
|
||||
throw new Exception("Name item with in same language with same value is already exists");
|
||||
nameItem.SetValue(value);
|
||||
}
|
||||
|
||||
private NameItem GetName(string nameId) => _shadow.names.FirstOrDefault(q => q.Id == nameId) ??
|
||||
throw new Exception($"Name with id [{nameId}] is not found");
|
||||
#endregion
|
||||
|
||||
internal void SetPreview(string url, MediaInfoType type)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url));
|
||||
_shadow.preview ??= new MediaInfo(url, type);
|
||||
_shadow.preview.SetUrl(url);
|
||||
}
|
||||
internal void DeletePreview()
|
||||
{
|
||||
_shadow.preview = null;
|
||||
}
|
||||
|
||||
#region Description
|
||||
internal void AddOriginalDescription(string languageId, string value)
|
||||
{
|
||||
if (_shadow.descriptions.Any(q => q.IsOriginal)) throw new Exception("Original description is already exist.");
|
||||
var description = new DescriptionItem(languageId, true);
|
||||
_shadow.descriptions.Add(description);
|
||||
SetDescriptionValue(description, value);
|
||||
}
|
||||
|
||||
internal void AddNotOriginalDescription(string languageId, string value)
|
||||
{
|
||||
var description = new DescriptionItem(languageId, false);
|
||||
_shadow.descriptions.Add(description);
|
||||
SetDescriptionValue(description, value);
|
||||
}
|
||||
|
||||
internal void RemoveDescription(string descriptionId)
|
||||
{
|
||||
var description = GetDescription(descriptionId);
|
||||
if (description.IsOriginal) throw new Exception($"Unable to remove original description");
|
||||
//_descriptions.Remove(name);
|
||||
description.SetDeleted();
|
||||
}
|
||||
|
||||
internal void SetDescriptionValue(string descriptionId, string value)
|
||||
{
|
||||
var name = GetDescription(descriptionId);
|
||||
SetDescriptionValue(name, value);
|
||||
}
|
||||
|
||||
private void SetDescriptionValue(DescriptionItem descriptionItem, string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentNullException(nameof(value));
|
||||
if (!descriptionItem.IsOriginal && _shadow.descriptions.Any(q => q.LanguageId == descriptionItem.LanguageId && q.Value == value))
|
||||
throw new Exception("Descriptoin item with with same value is already exists");
|
||||
descriptionItem.SetValue(value);
|
||||
}
|
||||
|
||||
private DescriptionItem GetDescription(string descriptionId)
|
||||
{
|
||||
return _shadow.descriptions.FirstOrDefault(q => q.Id == descriptionId) ??
|
||||
throw new Exception($"Description with id [{descriptionId}] is not found");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Genre
|
||||
internal void AddGenre(string genreId, decimal? proportion = null)
|
||||
{
|
||||
if (_shadow.genres.Any(q => q.GenreId == genreId)) throw new Exception("Genre is already in the list");
|
||||
var genreProportionItem = new GenreProportionItem(genreId);
|
||||
_shadow.genres.Add(genreProportionItem);
|
||||
if (proportion.HasValue) genreProportionItem.SetProportion(proportion);
|
||||
}
|
||||
|
||||
internal void SetGenreProportion(string genreProportionItemId, decimal? value = null)
|
||||
{
|
||||
var genreProportionItem = GetGenreProportionItem(genreProportionItemId);
|
||||
genreProportionItem.SetProportion(value);
|
||||
}
|
||||
|
||||
internal void RemoveGenre(string genreProportionItemId)
|
||||
{
|
||||
var genreProportionItem = GetGenreProportionItem(genreProportionItemId);
|
||||
//_genres.Remove(genreProportionItem);
|
||||
genreProportionItem.SetDeleted();
|
||||
}
|
||||
|
||||
private GenreProportionItem GetGenreProportionItem(string genreProportionItemId)
|
||||
{
|
||||
return _shadow.genres.FirstOrDefault(q => q.Id == genreProportionItemId) ??
|
||||
throw new Exception($"Genre proportion item with id [{genreProportionItemId}] is not found");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region RelatedContent
|
||||
|
||||
internal void AddRelatedContent(string url, MediaInfoType type)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url));
|
||||
CheckIfCanAddOrEdit(url, type);
|
||||
_shadow.relatedContent.Add(new MediaInfo(url, type));
|
||||
}
|
||||
|
||||
internal void EditRelatedContent(string relatedContentId, string url, MediaInfoType type)
|
||||
{
|
||||
var relatedContent = GetRelatedContent(relatedContentId);
|
||||
CheckIfCanAddOrEdit(url, type);
|
||||
relatedContent.SetUrl(url);
|
||||
relatedContent.SetType(type);
|
||||
}
|
||||
|
||||
internal void RemoveRelatedContent(string relatedContentId)
|
||||
{
|
||||
var relatedContent = GetRelatedContent(relatedContentId);
|
||||
//_relatedContent.Remove(relatedContent);
|
||||
relatedContent.SetDeleted();
|
||||
}
|
||||
|
||||
private void CheckIfCanAddOrEdit(string url, MediaInfoType type)
|
||||
{
|
||||
if (_shadow.relatedContent.Any(q => q.Url == url && q.Type == type))
|
||||
throw new Exception("Related content with same url and same type is already exists");
|
||||
}
|
||||
|
||||
|
||||
private MediaInfo GetRelatedContent(string relatedContentId)
|
||||
{
|
||||
return _shadow.relatedContent.FirstOrDefault(q => q.Id == relatedContentId) ??
|
||||
throw new Exception($"Related content with id [{relatedContentId}] is not found");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
internal void SetAnnouncementDate(DateTimeOffset value)
|
||||
{
|
||||
if (value == default) throw new ArgumentNullException(nameof(value));
|
||||
_shadow.announcementDate = value;
|
||||
AfterAnnouncementDateSet?.Invoke();
|
||||
}
|
||||
|
||||
internal void SetEstimatedReleaseDate(DateTimeOffset value)
|
||||
{
|
||||
if (value == default) throw new ArgumentNullException(nameof(value));
|
||||
if (_shadow.announcementDate.HasValue && value <= _shadow.announcementDate.Value)
|
||||
throw new Exception("Estimated release date can not be less or equal to announcement date");
|
||||
_shadow.estimatedReleaseDate = value;
|
||||
AfterEstimatedReleaseDateSet?.Invoke();
|
||||
}
|
||||
internal void SetReleaseDate(DateTimeOffset value)
|
||||
{
|
||||
if (value == default) throw new ArgumentNullException(nameof(value));
|
||||
if (_shadow.announcementDate.HasValue && value <= _shadow.announcementDate.Value)
|
||||
throw new Exception("Release date can not be less or equal to announcement date");
|
||||
_shadow.releaseDate = value;
|
||||
AfterReleaseDateSet?.Invoke();
|
||||
}
|
||||
|
||||
internal override void SetDeleted() { }
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.CommonProperties;
|
||||
public class DescriptionItem : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; private set; } = string.Empty;
|
||||
public bool IsOriginal { get; init; }
|
||||
[Required]
|
||||
public string LanguageId { get; init; } = default!;
|
||||
|
||||
internal DescriptionItem(string languageId, bool isOriginal)
|
||||
{
|
||||
LanguageId = languageId;
|
||||
IsOriginal = isOriginal;
|
||||
}
|
||||
|
||||
internal DescriptionItem(string languageId, bool isOriginal, string value)
|
||||
{
|
||||
LanguageId = languageId;
|
||||
IsOriginal = isOriginal;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public void SetValue(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.CommonProperties;
|
||||
|
||||
public class GenreProportionItem : Entity
|
||||
{
|
||||
public decimal? Proportion { get; private set; }
|
||||
[Required]
|
||||
public string GenreId { get; init; } = default!;
|
||||
|
||||
internal GenreProportionItem(string genreId) { GenreId = genreId; }
|
||||
|
||||
internal GenreProportionItem(string genreId, decimal? proportion)
|
||||
{
|
||||
GenreId = genreId;
|
||||
Proportion = proportion;
|
||||
}
|
||||
|
||||
public void SetProportion(decimal? proportion)
|
||||
{
|
||||
Proportion = proportion;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.CommonProperties;
|
||||
|
||||
public class NameItem : Entity
|
||||
{
|
||||
[Required]
|
||||
public string Value { get; private set; } = string.Empty;
|
||||
public NameType Type { get; init; }
|
||||
[Required]
|
||||
public string LanguageId { get; init; } = default!;
|
||||
|
||||
internal NameItem(string languageId, NameType type)
|
||||
{
|
||||
LanguageId = languageId;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
internal NameItem(string languageId, NameType type, string value)
|
||||
{
|
||||
LanguageId = languageId;
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public void SetValue(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum NameType
|
||||
{
|
||||
Original,
|
||||
OriginalInAnotherLanguage,
|
||||
Translation,
|
||||
Abbreviation,
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.CommonProperties.ShadowEntities;
|
||||
|
||||
internal class CommonPropertiesShadow
|
||||
{
|
||||
internal List<NameItem> names = [];
|
||||
internal MediaInfo? preview = null;
|
||||
internal List<DescriptionItem> descriptions = [];
|
||||
internal List<GenreProportionItem> genres = [];
|
||||
internal List<MediaInfo> relatedContent = [];
|
||||
internal DateTimeOffset? announcementDate = null;
|
||||
internal DateTimeOffset? estimatedReleaseDate = null;
|
||||
internal DateTimeOffset? releaseDate = null;
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
using Modules.Library.Domain.EntitiesV0.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.Items.Anime;
|
||||
|
||||
public class AnimeEpisode : AnimeItemSingle
|
||||
{
|
||||
private readonly AnimeTitle _title;
|
||||
private readonly AnimeSeason? _season;
|
||||
private readonly AnimeEpisodeShadow _shadow;
|
||||
|
||||
public AnimeEpisodeType Type => _shadow.type;
|
||||
public int? Number => _shadow.number;
|
||||
|
||||
internal AnimeEpisode(AnimeTitle title, AnimeSeason? season, AnimeEpisodeShadow shadow) : base(shadow)
|
||||
{
|
||||
_title = title;
|
||||
_season = season;
|
||||
_shadow = shadow;
|
||||
}
|
||||
|
||||
public void SetNumber(int? number) => _shadow.number = number;
|
||||
|
||||
public override void SetOrder(ushort order)
|
||||
{
|
||||
if (_season == null) SetItemOrder(_title._shadow.items, Order, order);
|
||||
else _season.SetEpisodeOrder(Order, order);
|
||||
}
|
||||
|
||||
public override void SetVariant(ushort variant)
|
||||
{
|
||||
if (_season == null)
|
||||
{
|
||||
SetItemVariant(_title._shadow.items.OfType<AnimeEpisodeShadow>()
|
||||
.Where(q => q.order == Order), Variant, variant);
|
||||
}
|
||||
else _season.SetEpisodeVariant(Order, Variant, variant);
|
||||
}
|
||||
|
||||
public override void SetCompleted()
|
||||
{
|
||||
if (_season == null) _title.SetEpisodeCompleted(Order, Variant, true);
|
||||
else _season.SetEpisodeCompleted(Order, Variant, true);
|
||||
}
|
||||
|
||||
public override void SetNotCompleted()
|
||||
{
|
||||
if (_season == null) _title.SetEpisodeCompleted(Order, Variant, false);
|
||||
else _season.SetEpisodeCompleted(Order, Variant, false);
|
||||
}
|
||||
|
||||
public override void SetExpirationTime(TimeSpan value)
|
||||
{
|
||||
if (_season == null) _title.SetEpisodeExpirationTime(Order, Variant, value);
|
||||
else _season.SetEpisodeExpirationTime(Order, Variant, value);
|
||||
}
|
||||
|
||||
internal void SetDeleted() => _shadow.deleted = true;
|
||||
}
|
||||
|
||||
public enum AnimeEpisodeType
|
||||
{
|
||||
Regilar,
|
||||
FullLength,
|
||||
Ova,
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
using Modules.Library.Domain.EntitiesV0.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.Items.Anime;
|
||||
|
||||
public abstract class AnimeItem : Entity
|
||||
{
|
||||
private readonly AnimeItemShadow _shadow;
|
||||
|
||||
public override string Id => _shadow.id;
|
||||
|
||||
public CommonProperties.CommonProperties CommonProperties => GetCommonProperties();
|
||||
public ushort Order => _shadow.order;
|
||||
public ushort Variant => _shadow.variant;
|
||||
public bool Completed => _shadow.completed;
|
||||
public override bool Deleted => _shadow.deleted;
|
||||
public TimeSpan ExpirationTime => _shadow.expirationTime;
|
||||
|
||||
internal AnimeItem(AnimeItemShadow shadow) { _shadow = shadow; }
|
||||
|
||||
/*
|
||||
private CommonProperties.CommonProperties GetCommonProperties()
|
||||
{
|
||||
new (shadow.commonProperties, afterAnnouncementDateSet, afterEstimatedReleaseDateSet, afterReleaseDateSet)
|
||||
}
|
||||
*/
|
||||
|
||||
protected virtual CommonProperties.CommonProperties GetCommonProperties() =>
|
||||
new(_shadow.commonProperties, null, null, null);
|
||||
|
||||
public abstract void SetOrder(ushort order);
|
||||
internal static ushort GetNewOrder(IEnumerable<AnimeItemShadow> items) =>
|
||||
(ushort)(items.Select(q => (int)q.order).DefaultIfEmpty(-1).Max() + 1);
|
||||
|
||||
public abstract void SetVariant(ushort variant);
|
||||
internal static ushort GetNewVariant(IEnumerable<AnimeItemShadow> items, ushort order) =>
|
||||
(ushort)(items.Where(q => q.order == order)
|
||||
.Select(q => (int)q.variant).DefaultIfEmpty(-1).Max() + 1);
|
||||
|
||||
internal static void Sort(IEnumerable<AnimeItemShadow> items)
|
||||
{
|
||||
var orderedItemGroups = items.GroupBy(q => q.order).Select(q => new { Order = q.Key, Items = items.Where(x => x.order == q.Key) })
|
||||
.OrderBy(q => q.Order).ToArray();
|
||||
for (int i = 0; i < orderedItemGroups.Length; i++)
|
||||
{
|
||||
foreach (var item in orderedItemGroups[i].Items)
|
||||
{
|
||||
item.order = (ushort)i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void SetCompleted();
|
||||
public abstract void SetNotCompleted();
|
||||
public abstract void SetExpirationTime(TimeSpan value);
|
||||
|
||||
internal static void SetItemOrder(IEnumerable<AnimeItemShadow> items, ushort lastOrder, ushort newOrder)
|
||||
{
|
||||
if (newOrder < 0 || newOrder > items.Select(q => q.order).DefaultIfEmpty<ushort>(0).Max())
|
||||
throw new ArgumentOutOfRangeException(nameof(newOrder));
|
||||
//if (lastOrder == newOrder) return;
|
||||
var newOrderItem = items.First(q => q.order == newOrder);
|
||||
items.First(q => q.order == lastOrder).order = newOrder;
|
||||
foreach (var item in items.Where(q => q.order > Math.Min(lastOrder, newOrder) && q.order < Math.Max(lastOrder, newOrder)))
|
||||
{
|
||||
if (lastOrder > newOrder) item.order++;
|
||||
else if (lastOrder < newOrder) item.order--;
|
||||
}
|
||||
if (lastOrder > newOrder) newOrderItem.order++;
|
||||
else if (lastOrder < newOrder) newOrderItem.order--;
|
||||
}
|
||||
|
||||
internal static void SetItemVariant(IEnumerable<AnimeItemShadow> items, ushort lastVariant, ushort newVariant)
|
||||
{
|
||||
if (newVariant < 0 || newVariant >= items.Count())
|
||||
throw new ArgumentOutOfRangeException(nameof(newVariant));
|
||||
//if (lastVariant == newVariant) return;
|
||||
var newVariantItem = items.First(q => q.variant == newVariant);
|
||||
items.First(q => q.variant == lastVariant).variant = newVariant;
|
||||
foreach (var item in items.Where(q => q.variant > Math.Min(lastVariant, newVariant) && q.variant < Math.Max(lastVariant, newVariant)))
|
||||
{
|
||||
if (lastVariant > newVariant) item.variant++;
|
||||
else if (lastVariant < newVariant) item.variant--;
|
||||
}
|
||||
if (lastVariant > newVariant) newVariantItem.variant++;
|
||||
else if (lastVariant < newVariant) newVariantItem.variant--;
|
||||
}
|
||||
|
||||
internal override void SetDeleted() => _shadow.deleted = true;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using Modules.Library.Domain.EntitiesV0.MediaContent.Items.Anime.ShadowEntities;
|
||||
|
||||
namespace Modules.Library.Domain.EntitiesV0.MediaContent.Items.Anime;
|
||||
|
||||
public abstract class AnimeItemSingle : AnimeItem
|
||||
{
|
||||
private readonly AnimeItemSingleShadow _shadow;
|
||||
public TimeSpan? Duration => _shadow.duration;
|
||||
|
||||
internal AnimeItemSingle(AnimeItemSingleShadow shadow) : base(shadow)
|
||||
{
|
||||
_shadow = shadow;
|
||||
}
|
||||
|
||||
public void SetDuration(TimeSpan value) => _shadow.duration = value;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user