Added additional voice work update logic.
This commit is contained in:
7
JSMR.Application/Common/ISupportedLanguage.cs
Normal file
7
JSMR.Application/Common/ISupportedLanguage.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace JSMR.Application.Common;
|
||||||
|
|
||||||
|
public interface ISupportedLanguage
|
||||||
|
{
|
||||||
|
string Code { get; }
|
||||||
|
Language Language { get; }
|
||||||
|
}
|
||||||
@@ -6,10 +6,11 @@ public class VoiceWorkDetails
|
|||||||
{
|
{
|
||||||
public VoiceWorkSeries? Series { get; init; }
|
public VoiceWorkSeries? Series { get; init; }
|
||||||
public VoiceWorkTranslation? Translation { get; init; }
|
public VoiceWorkTranslation? Translation { get; init; }
|
||||||
|
public AgeRating AgeRating { get; init; }
|
||||||
public int WishlistCount { get; init; }
|
public int WishlistCount { get; init; }
|
||||||
public int DownloadCount { get; init; }
|
public int DownloadCount { get; init; }
|
||||||
public DateTime? RegistrationDate { get; init; }
|
public DateTime? RegistrationDate { get; init; }
|
||||||
public Language[] SupportedLanguages { get; init; } = [];
|
public ISupportedLanguage[] SupportedLanguages { get; init; } = [];
|
||||||
public AIGeneration AI { get; init; }
|
public AIGeneration AI { get; init; }
|
||||||
public bool HasTrial { get; init; }
|
public bool HasTrial { get; init; }
|
||||||
public bool HasDLPlay { get; init; }
|
public bool HasDLPlay { get; init; }
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace JSMR.Application.Scanning.Contracts;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Application.Scanning.Contracts;
|
||||||
|
|
||||||
public class DLSiteWork
|
public class DLSiteWork
|
||||||
{
|
{
|
||||||
@@ -19,6 +21,7 @@ public class DLSiteWork
|
|||||||
public bool HasTrial { get; set;}
|
public bool HasTrial { get; set;}
|
||||||
public ICollection<string> Tags { get; set; } = [];
|
public ICollection<string> Tags { get; set; } = [];
|
||||||
public ICollection<string> Creators { get; set; } = [];
|
public ICollection<string> Creators { get; set; } = [];
|
||||||
public string? ImageUrl { get; set; }
|
public required string ImageUrl { get; set; }
|
||||||
public string? SmallImageUrl { get; set; }
|
public required string SmallImageUrl { get; set; }
|
||||||
|
public AgeRating AgeRating { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using JSMR.Application.Integrations.DLSite.Models;
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
|
||||||
namespace JSMR.Application.Scanning.Contracts;
|
namespace JSMR.Application.Scanning.Contracts;
|
||||||
|
|
||||||
@@ -17,5 +18,11 @@ public record VoiceWorkIngest(DLSiteWork Work, VoiceWorkDetails? Details)
|
|||||||
public bool HasDLPlay { get; init; } = Details?.HasDLPlay ?? false;
|
public bool HasDLPlay { get; init; } = Details?.HasDLPlay ?? false;
|
||||||
public byte? StarRating { get; init; } = Work.StarRating;
|
public byte? StarRating { get; init; } = Work.StarRating;
|
||||||
public int? Votes { get; init; } = Work.Votes;
|
public int? Votes { get; init; } = Work.Votes;
|
||||||
|
public AgeRating AgeRating { get; init; } = Details?.AgeRating ?? Work.AgeRating;
|
||||||
|
public bool HasImage { get; init; } = Work.ImageUrl.Contains("no_img") == false;
|
||||||
|
public ICollection<ISupportedLanguage> SupportedLanguages { get; init; } = Details?.SupportedLanguages ?? [];
|
||||||
|
public DateOnly? ExpectedDate { get; init; } = Work.ExpectedDate;
|
||||||
|
public DateOnly? SalesDate { get; init; } = Work.SalesDate;
|
||||||
|
public DateTime? RegistrationDate { get; init; } = Details?.RegistrationDate;
|
||||||
// TODO: Other properties
|
// TODO: Other properties
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ public class VoiceWork
|
|||||||
public bool? IsValid { get; set; }
|
public bool? IsValid { get; set; }
|
||||||
public DateTime? LastScannedDate { get; set; }
|
public DateTime? LastScannedDate { get; set; }
|
||||||
public byte Status { get; set; }
|
public byte Status { get; set; }
|
||||||
public byte SubtitleLanguage { get; set; }
|
public byte SubtitleLanguage { get; set; } // TO BE DEPRECATED
|
||||||
public bool HasChobit { get; set; }
|
public bool HasChobit { get; set; }
|
||||||
public bool IsPurchased { get; set; }
|
public bool IsPurchased { get; set; }
|
||||||
public int? WishlistCount { get; set; }
|
public int? WishlistCount { get; set; }
|
||||||
@@ -36,8 +36,9 @@ public class VoiceWork
|
|||||||
//public int? VoiceWorkSearchId { get; set; }
|
//public int? VoiceWorkSearchId { get; set; }
|
||||||
//public VoiceWorkSearch? VoiceWorkSearch { get; set; }
|
//public VoiceWorkSearch? VoiceWorkSearch { get; set; }
|
||||||
|
|
||||||
public virtual ICollection<VoiceWorkTag> VoiceWorkTags { get; set; } = [];
|
public virtual ICollection<VoiceWorkTag> Tags { get; set; } = [];
|
||||||
public virtual ICollection<VoiceWorkCreator> VoiceWorkCreators { get; set; } = [];
|
public virtual ICollection<VoiceWorkCreator> Creators { get; set; } = [];
|
||||||
public virtual ICollection<EnglishVoiceWork> EnglishVoiceWorks { get; set; } = [];
|
public virtual ICollection<EnglishVoiceWork> EnglishVoiceWorks { get; set; } = [];
|
||||||
|
public virtual ICollection<VoiceWorkSupportedLanguage> SupportedLanguages { get; set; } = [];
|
||||||
public virtual ICollection<VoiceWorkLocalization> Localizations { get; set; } = [];
|
public virtual ICollection<VoiceWorkLocalization> Localizations { get; set; } = [];
|
||||||
}
|
}
|
||||||
10
JSMR.Domain/Entities/VoiceWorkSupportedLanguage.cs
Normal file
10
JSMR.Domain/Entities/VoiceWorkSupportedLanguage.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace JSMR.Domain.Entities;
|
||||||
|
|
||||||
|
public sealed class VoiceWorkSupportedLanguage
|
||||||
|
{
|
||||||
|
public int VoiceWorkSupportedLanguageId { get; set; }
|
||||||
|
public int VoiceWorkId { get; set; }
|
||||||
|
public string Language { get; set; } = null!;
|
||||||
|
|
||||||
|
public VoiceWork VoiceWork { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.Languages;
|
||||||
|
|
||||||
|
public interface ILanguageIdentifier
|
||||||
|
{
|
||||||
|
Language GetLanguage(string text);
|
||||||
|
}
|
||||||
56094
JSMR.Infrastructure/Common/Languages/Language.xml
Normal file
56094
JSMR.Infrastructure/Common/Languages/Language.xml
Normal file
File diff suppressed because it is too large
Load Diff
44
JSMR.Infrastructure/Common/Languages/LanguageIdentifier.cs
Normal file
44
JSMR.Infrastructure/Common/Languages/LanguageIdentifier.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
using NTextCat;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.Languages;
|
||||||
|
|
||||||
|
public class LanguageIdentifier : ILanguageIdentifier
|
||||||
|
{
|
||||||
|
private readonly string[] _languages =
|
||||||
|
[
|
||||||
|
"eng",
|
||||||
|
"jpn",
|
||||||
|
"kor",
|
||||||
|
"zho"
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly RankedLanguageIdentifier _identifier;
|
||||||
|
|
||||||
|
public LanguageIdentifier()
|
||||||
|
{
|
||||||
|
RankedLanguageIdentifierFactory factory = new();
|
||||||
|
|
||||||
|
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("JSMR.Infrastructure.Languages.Language.xml");
|
||||||
|
_identifier = factory.Load(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Language GetLanguage(string text)
|
||||||
|
{
|
||||||
|
var rankedLanguages = _identifier.Identify(text).Where(x => _languages.Contains(x.Item1.Iso639_3));
|
||||||
|
var identifiedLanguage = rankedLanguages.OrderBy(x => x.Item2).FirstOrDefault();
|
||||||
|
|
||||||
|
if (identifiedLanguage == null)
|
||||||
|
return Language.Unknown;
|
||||||
|
|
||||||
|
return identifiedLanguage.Item1.Iso639_3 switch
|
||||||
|
{
|
||||||
|
"jpn" => Language.Japanese,
|
||||||
|
"eng" => Language.English,
|
||||||
|
"kor" => Language.Korean,
|
||||||
|
"zho" => Language.ChineseTraditional,// Or ChineseSimplified?
|
||||||
|
_ => Language.Unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class AlingualLanguage : ISupportedLanguage
|
public class AlingualLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
|
public Language Language => Language.Unknown;
|
||||||
public string Code => "NM";
|
public string Code => "NM";
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class ChineseLanguage : ISupportedLanguage
|
public class ChineseLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
|
public Language Language => Language.ChineseTraditional; // ???
|
||||||
public string Code => "CHI";
|
public string Code => "CHI";
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class DLSiteOfficialTranslationLanguage : ISupportedLanguage
|
public class DLSiteOfficialTranslationLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
public string Code => "DOT";
|
public string Code => "DOT";
|
||||||
|
public Language Language => Language.Unknown;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class EnglishLanguage : ISupportedLanguage
|
public class EnglishLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
|
public Language Language => Language.English;
|
||||||
public string Code => "ENG";
|
public string Code => "ENG";
|
||||||
}
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
|
||||||
|
|
||||||
public interface ISupportedLanguage
|
|
||||||
{
|
|
||||||
string Code { get; }
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class JapaneseLanguage : ISupportedLanguage
|
public class JapaneseLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
|
public Language Language => Language.Japanese;
|
||||||
public string Code => "JPN";
|
public string Code => "JPN";
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class KoreanLanguage : ISupportedLanguage
|
public class KoreanLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
|
public Language Language => Language.Korean;
|
||||||
public string Code => "KO_KR";
|
public string Code => "KO_KR";
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class SimplifiedChineseLanguage : ISupportedLanguage
|
public class SimplifiedChineseLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
|
public Language Language => Language.ChineseSimplified;
|
||||||
public string Code => "CHI_HANS";
|
public string Code => "CHI_HANS";
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
public class TraditionalChineseLanguage : ISupportedLanguage
|
public class TraditionalChineseLanguage : ISupportedLanguage
|
||||||
{
|
{
|
||||||
|
public Language Language => Language.ChineseTraditional;
|
||||||
public string Code => "CHI_HANT";
|
public string Code => "CHI_HANT";
|
||||||
}
|
}
|
||||||
6
JSMR.Infrastructure/Common/Time/Clock.cs
Normal file
6
JSMR.Infrastructure/Common/Time/Clock.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Infrastructure.Common.Time;
|
||||||
|
|
||||||
|
public class Clock : IClock
|
||||||
|
{
|
||||||
|
public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
6
JSMR.Infrastructure/Common/Time/IClock.cs
Normal file
6
JSMR.Infrastructure/Common/Time/IClock.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Infrastructure.Common.Time;
|
||||||
|
|
||||||
|
public interface IClock
|
||||||
|
{
|
||||||
|
DateTimeOffset UtcNow { get; }
|
||||||
|
}
|
||||||
69
JSMR.Infrastructure/Common/Time/TimeProvider.cs
Normal file
69
JSMR.Infrastructure/Common/Time/TimeProvider.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
namespace JSMR.Infrastructure.Common.Time;
|
||||||
|
|
||||||
|
public interface ITimeProvider
|
||||||
|
{
|
||||||
|
DateTimeOffset Now();
|
||||||
|
DateTimeOffset Local(int year, int month, int day, int hour);
|
||||||
|
DateTimeOffset Local(DateTimeOffset offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class TimeProvider : ITimeProvider
|
||||||
|
{
|
||||||
|
protected abstract string Id { get; }
|
||||||
|
protected abstract string[] TimeZoneIds { get; }
|
||||||
|
|
||||||
|
private readonly IClock _clock;
|
||||||
|
private readonly TimeZoneInfo _timeZone;
|
||||||
|
|
||||||
|
public TimeProvider(IClock clock)
|
||||||
|
{
|
||||||
|
_clock = clock;
|
||||||
|
_timeZone = ResolveTimeZone();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeZoneInfo ResolveTimeZone()
|
||||||
|
{
|
||||||
|
foreach (string timeZoneId in TimeZoneIds)
|
||||||
|
{
|
||||||
|
if (TimeZoneInfo.TryFindSystemTimeZoneById(timeZoneId, out TimeZoneInfo? timeZoneInfo))
|
||||||
|
return timeZoneInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TimeZoneNotFoundException($"Unable to resolve time zone for: {Id} ({string.Join(" / ", TimeZoneIds)})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeOffset Now() => TimeZoneInfo.ConvertTime(_clock.UtcNow, _timeZone);
|
||||||
|
public DateTimeOffset Local(DateTimeOffset offset) => TimeZoneInfo.ConvertTime(offset, _timeZone);
|
||||||
|
|
||||||
|
public DateTimeOffset Local(int year, int month, int day, int hour)
|
||||||
|
{
|
||||||
|
DateTime local = new(year, month, day, hour, 0, 0, DateTimeKind.Unspecified);
|
||||||
|
TimeSpan offset = _timeZone.GetUtcOffset(local);
|
||||||
|
|
||||||
|
return new DateTimeOffset(local, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeOffset CurrentScanAnchor()
|
||||||
|
{
|
||||||
|
DateTimeOffset now = Now();
|
||||||
|
DateTimeOffset midnight = Local(now.Year, now.Month, now.Day, 0);
|
||||||
|
DateTimeOffset fourPm = Local(now.Year, now.Month, now.Day, 16);
|
||||||
|
|
||||||
|
return now >= fourPm ? fourPm : midnight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeOffset PreviousScanAnchor(DateTimeOffset scanAnchorTokyo)
|
||||||
|
{
|
||||||
|
// Normalize to Tokyo (no-op if already)
|
||||||
|
var a = TimeZoneInfo.ConvertTime(scanAnchorTokyo, _timeZone);
|
||||||
|
return a.Hour == 16
|
||||||
|
? Local(a.Year, a.Month, a.Day, 0)
|
||||||
|
: Local(a.AddDays(-1).Year, a.AddDays(-1).Month, a.AddDays(-1).Day, 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TokyoTimeProvider(IClock clock) : TimeProvider(clock)
|
||||||
|
{
|
||||||
|
protected override string Id => "Tokyo Standard Time";
|
||||||
|
protected override string[] TimeZoneIds => ["Tokyo Standard Time", "Asia/Tokyo"];
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ using JSMR.Application.Common;
|
|||||||
using JSMR.Application.Common.Caching;
|
using JSMR.Application.Common.Caching;
|
||||||
using JSMR.Application.Creators.Ports;
|
using JSMR.Application.Creators.Ports;
|
||||||
using JSMR.Application.Creators.Queries.Search.Ports;
|
using JSMR.Application.Creators.Queries.Search.Ports;
|
||||||
using JSMR.Application.Integrations.Ports;
|
|
||||||
using JSMR.Application.Scanning.Ports;
|
using JSMR.Application.Scanning.Ports;
|
||||||
using JSMR.Application.Tags.Ports;
|
using JSMR.Application.Tags.Ports;
|
||||||
using JSMR.Application.Tags.Queries.Search.Ports;
|
using JSMR.Application.Tags.Queries.Search.Ports;
|
||||||
@@ -13,12 +12,14 @@ using JSMR.Application.VoiceWorks.Ports;
|
|||||||
using JSMR.Application.VoiceWorks.Queries.Search;
|
using JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
using JSMR.Infrastructure.Caching;
|
using JSMR.Infrastructure.Caching;
|
||||||
using JSMR.Infrastructure.Caching.Adapters;
|
using JSMR.Infrastructure.Caching.Adapters;
|
||||||
|
using JSMR.Infrastructure.Common.Languages;
|
||||||
|
using JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
using JSMR.Infrastructure.Common.Time;
|
||||||
using JSMR.Infrastructure.Data.Repositories.Circles;
|
using JSMR.Infrastructure.Data.Repositories.Circles;
|
||||||
using JSMR.Infrastructure.Data.Repositories.Creators;
|
using JSMR.Infrastructure.Data.Repositories.Creators;
|
||||||
using JSMR.Infrastructure.Data.Repositories.Tags;
|
using JSMR.Infrastructure.Data.Repositories.Tags;
|
||||||
using JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
using JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
using JSMR.Infrastructure.Http;
|
using JSMR.Infrastructure.Http;
|
||||||
using JSMR.Infrastructure.Integrations.DLSite;
|
|
||||||
using JSMR.Infrastructure.Scanning;
|
using JSMR.Infrastructure.Scanning;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -38,6 +39,12 @@ public static class InfrastructureServiceCollectionExtensions
|
|||||||
services.AddKeyedScoped<IVoiceWorksScanner, JapaneseVoiceWorksScanner>(Locale.Japanese);
|
services.AddKeyedScoped<IVoiceWorksScanner, JapaneseVoiceWorksScanner>(Locale.Japanese);
|
||||||
services.AddKeyedScoped<IVoiceWorksScanner, EnglishVoiceWorksScanner>(Locale.English);
|
services.AddKeyedScoped<IVoiceWorksScanner, EnglishVoiceWorksScanner>(Locale.English);
|
||||||
|
|
||||||
|
services.AddKeyedScoped<ISupportedLanguage, JapaneseLanguage>(Locale.Japanese);
|
||||||
|
services.AddKeyedScoped<ISupportedLanguage, EnglishLanguage>(Locale.English);
|
||||||
|
services.AddKeyedScoped<ISupportedLanguage, SimplifiedChineseLanguage>(Locale.ChineseSimplified);
|
||||||
|
services.AddKeyedScoped<ISupportedLanguage, TraditionalChineseLanguage>(Locale.ChineseTraditional);
|
||||||
|
services.AddKeyedScoped<ISupportedLanguage, KoreanLanguage>(Locale.Korean);
|
||||||
|
|
||||||
services.AddScoped<ITagSearchProvider, TagSearchProvider>();
|
services.AddScoped<ITagSearchProvider, TagSearchProvider>();
|
||||||
services.AddScoped<ITagWriter, TagWriter>();
|
services.AddScoped<ITagWriter, TagWriter>();
|
||||||
|
|
||||||
@@ -55,6 +62,11 @@ public static class InfrastructureServiceCollectionExtensions
|
|||||||
services.AddScoped<IHttpService, HttpService>();
|
services.AddScoped<IHttpService, HttpService>();
|
||||||
services.AddScoped<IHtmlLoader, HtmlLoader>();
|
services.AddScoped<IHtmlLoader, HtmlLoader>();
|
||||||
|
|
||||||
|
services.AddSingleton<ILanguageIdentifier, LanguageIdentifier>();
|
||||||
|
|
||||||
|
services.AddSingleton<IClock, Clock>();
|
||||||
|
services.AddSingleton<ITimeProvider, TokyoTimeProvider>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(op
|
|||||||
{
|
{
|
||||||
public DbSet<VoiceWork> VoiceWorks { get; set; }
|
public DbSet<VoiceWork> VoiceWorks { get; set; }
|
||||||
public DbSet<EnglishVoiceWork> EnglishVoiceWorks { get; set; }
|
public DbSet<EnglishVoiceWork> EnglishVoiceWorks { get; set; }
|
||||||
|
public DbSet<VoiceWorkSupportedLanguage> VoiceWorkSupportedLanguages { get; set; }
|
||||||
public DbSet<VoiceWorkLocalization> VoiceWorkLocalizations { get; set; }
|
public DbSet<VoiceWorkLocalization> VoiceWorkLocalizations { get; set; }
|
||||||
public DbSet<Circle> Circles { get; set; }
|
public DbSet<Circle> Circles { get; set; }
|
||||||
public DbSet<Tag> Tags { get; set; }
|
public DbSet<Tag> Tags { get; set; }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public sealed class VoiceWorkCreatorConfiguration : IEntityTypeConfiguration<Voi
|
|||||||
builder.HasKey(x => new { x.VoiceWorkId, x.CreatorId });
|
builder.HasKey(x => new { x.VoiceWorkId, x.CreatorId });
|
||||||
|
|
||||||
builder.HasOne(x => x.VoiceWork)
|
builder.HasOne(x => x.VoiceWork)
|
||||||
.WithMany(v => v.VoiceWorkCreators)
|
.WithMany(v => v.Creators)
|
||||||
.HasForeignKey(x => x.VoiceWorkId)
|
.HasForeignKey(x => x.VoiceWorkId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using JSMR.Domain.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Data.Configuration;
|
||||||
|
|
||||||
|
public sealed class VoiceWorkSupportedLanguageConfiguration : IEntityTypeConfiguration<VoiceWorkSupportedLanguage>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<VoiceWorkSupportedLanguage> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("voice_work_supported_languages");
|
||||||
|
builder.HasKey(x => x.VoiceWorkSupportedLanguageId);
|
||||||
|
|
||||||
|
builder.Property(x => x.Language).IsRequired().HasMaxLength(10);
|
||||||
|
|
||||||
|
builder.HasOne(x => x.VoiceWork)
|
||||||
|
.WithMany(v => v.SupportedLanguages)
|
||||||
|
.HasForeignKey(x => x.VoiceWorkId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasIndex(x => new { x.VoiceWorkId, x.Language }).IsUnique();
|
||||||
|
builder.HasIndex(x => x.VoiceWorkId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ public sealed class VoiceWorkTagConfiguration : IEntityTypeConfiguration<VoiceWo
|
|||||||
builder.HasKey(x => new { x.VoiceWorkId, x.TagId });
|
builder.HasKey(x => new { x.VoiceWorkId, x.TagId });
|
||||||
|
|
||||||
builder.HasOne(x => x.VoiceWork)
|
builder.HasOne(x => x.VoiceWork)
|
||||||
.WithMany(v => v.VoiceWorkTags)
|
.WithMany(v => v.Tags)
|
||||||
.HasForeignKey(x => x.VoiceWorkId)
|
.HasForeignKey(x => x.VoiceWorkId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public class VoiceWorkSearchUpdater(AppDbContext dbContext) : IVoiceWorkSearchUp
|
|||||||
{
|
{
|
||||||
List<VoiceWork> batch = await dbContext.VoiceWorks
|
List<VoiceWork> batch = await dbContext.VoiceWorks
|
||||||
.Include(vw => vw.Circle)
|
.Include(vw => vw.Circle)
|
||||||
.Include(vw => vw.VoiceWorkTags)
|
.Include(vw => vw.Tags)
|
||||||
.ThenInclude(vwt => vwt.Tag)
|
.ThenInclude(vwt => vwt.Tag)
|
||||||
.Include(vw => vw.VoiceWorkCreators)
|
.Include(vw => vw.Creators)
|
||||||
.ThenInclude(vwc => vwc.Creator)
|
.ThenInclude(vwc => vwc.Creator)
|
||||||
.Include(vw => vw.EnglishVoiceWorks)
|
.Include(vw => vw.EnglishVoiceWorks)
|
||||||
.Where(vw => voiceWorkIds.Contains(vw.VoiceWorkId))
|
.Where(vw => voiceWorkIds.Contains(vw.VoiceWorkId))
|
||||||
@@ -71,7 +71,7 @@ public class VoiceWorkSearchUpdater(AppDbContext dbContext) : IVoiceWorkSearchUp
|
|||||||
AppendRaw(sb, voiceWork.Description);
|
AppendRaw(sb, voiceWork.Description);
|
||||||
AppendRaw(sb, voiceWork.Circle?.Name);
|
AppendRaw(sb, voiceWork.Circle?.Name);
|
||||||
|
|
||||||
foreach (var tag in voiceWork.VoiceWorkTags.Select(vwt => vwt.Tag))
|
foreach (var tag in voiceWork.Tags.Select(vwt => vwt.Tag))
|
||||||
{
|
{
|
||||||
if (tag is null)
|
if (tag is null)
|
||||||
continue;
|
continue;
|
||||||
@@ -86,7 +86,7 @@ public class VoiceWorkSearchUpdater(AppDbContext dbContext) : IVoiceWorkSearchUp
|
|||||||
AppendRaw(sb, englishTag?.Name);
|
AppendRaw(sb, englishTag?.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var creator in voiceWork.VoiceWorkCreators.Select(vwc => vwc.Creator))
|
foreach (var creator in voiceWork.Creators.Select(vwc => vwc.Creator))
|
||||||
{
|
{
|
||||||
if (creator is null)
|
if (creator is null)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
|
|
||||||
public record VoiceWorkUpsertContext(
|
public record VoiceWorkUpsertContext(
|
||||||
|
DateTimeOffset CurrentScanAnchor,
|
||||||
|
DateTimeOffset PreviousScanAnchor,
|
||||||
Dictionary<string, Circle> Circles,
|
Dictionary<string, Circle> Circles,
|
||||||
Dictionary<string, VoiceWork> VoiceWorks,
|
Dictionary<string, VoiceWork> VoiceWorks,
|
||||||
Dictionary<string, Tag> Tags,
|
Dictionary<string, Tag> Tags,
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
using JSMR.Application.Scanning.Contracts;
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Application.Scanning.Contracts;
|
||||||
using JSMR.Application.VoiceWorks.Commands.SetFavorite;
|
using JSMR.Application.VoiceWorks.Commands.SetFavorite;
|
||||||
using JSMR.Application.VoiceWorks.Ports;
|
using JSMR.Application.VoiceWorks.Ports;
|
||||||
using JSMR.Domain.Entities;
|
using JSMR.Domain.Entities;
|
||||||
|
using JSMR.Infrastructure.Common.Time;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
|
|
||||||
public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
public class VoiceWorkWriter(AppDbContext dbContext, ITimeProvider timeProvider) : IVoiceWorkWriter
|
||||||
{
|
{
|
||||||
public async Task<int[]> UpsertAsync(IReadOnlyCollection<VoiceWorkIngest> ingests, CancellationToken cancellationToken)
|
public async Task<int[]> UpsertAsync(IReadOnlyCollection<VoiceWorkIngest> ingests, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -29,14 +31,21 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
string[] tagNames = [.. ingests.SelectMany(i => i.Tags).Distinct()];
|
string[] tagNames = [.. ingests.SelectMany(i => i.Tags).Distinct()];
|
||||||
string[] creatorNames = [.. ingests.SelectMany(i => i.Creators).Distinct()];
|
string[] creatorNames = [.. ingests.SelectMany(i => i.Creators).Distinct()];
|
||||||
|
|
||||||
|
DateTimeOffset currentScanAnchor = GetCurrentScanAnchor();
|
||||||
|
DateTimeOffset previousScanAnchor = PreviousScanAnchor(currentScanAnchor);
|
||||||
|
|
||||||
VoiceWorkUpsertContext upsertContext = new(
|
VoiceWorkUpsertContext upsertContext = new(
|
||||||
|
CurrentScanAnchor: currentScanAnchor,
|
||||||
|
PreviousScanAnchor: previousScanAnchor,
|
||||||
Circles: await dbContext.Circles
|
Circles: await dbContext.Circles
|
||||||
.Where(c => makerIds.Contains(c.MakerId))
|
.Where(c => makerIds.Contains(c.MakerId))
|
||||||
.ToDictionaryAsync(c => c.MakerId, cancellationToken),
|
.ToDictionaryAsync(c => c.MakerId, cancellationToken),
|
||||||
VoiceWorks: await dbContext.VoiceWorks
|
VoiceWorks: await dbContext.VoiceWorks
|
||||||
.Where(v => productIds.Contains(v.ProductId))
|
.Where(v => productIds.Contains(v.ProductId))
|
||||||
.Include(v => v.VoiceWorkCreators)
|
.Include(v => v.Creators)
|
||||||
.Include(v => v.VoiceWorkTags)
|
.Include(v => v.Tags)
|
||||||
|
.Include(v => v.Localizations)
|
||||||
|
.Include(v => v.SupportedLanguages)
|
||||||
.ToDictionaryAsync(v => v.ProductId, cancellationToken),
|
.ToDictionaryAsync(v => v.ProductId, cancellationToken),
|
||||||
Tags: await dbContext.Tags
|
Tags: await dbContext.Tags
|
||||||
.Where(t => tagNames.Contains(t.Name))
|
.Where(t => tagNames.Contains(t.Name))
|
||||||
@@ -49,6 +58,25 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
return upsertContext;
|
return upsertContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DateTimeOffset GetCurrentScanAnchor()
|
||||||
|
{
|
||||||
|
DateTimeOffset now = timeProvider.Now();
|
||||||
|
DateTimeOffset midnight = timeProvider.Local(now.Year, now.Month, now.Day, 0);
|
||||||
|
DateTimeOffset fourPm = timeProvider.Local(now.Year, now.Month, now.Day, 16);
|
||||||
|
|
||||||
|
return now >= fourPm ? fourPm : midnight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTimeOffset PreviousScanAnchor(DateTimeOffset scanAnchorTokyo)
|
||||||
|
{
|
||||||
|
// Normalize to Tokyo (no-op if already)
|
||||||
|
var a = timeProvider.Local(scanAnchorTokyo);
|
||||||
|
|
||||||
|
return a.Hour == 16
|
||||||
|
? timeProvider.Local(a.Year, a.Month, a.Day, 0)
|
||||||
|
: timeProvider.Local(a.AddDays(-1).Year, a.AddDays(-1).Month, a.AddDays(-1).Day, 16);
|
||||||
|
}
|
||||||
|
|
||||||
private void Upsert(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
private void Upsert(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
{
|
{
|
||||||
UpsertCircle(ingest, upsertContext);
|
UpsertCircle(ingest, upsertContext);
|
||||||
@@ -57,6 +85,7 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
UpsertVoiceWorkTags(ingest, upsertContext);
|
UpsertVoiceWorkTags(ingest, upsertContext);
|
||||||
UpsertCreators(ingest, upsertContext);
|
UpsertCreators(ingest, upsertContext);
|
||||||
UpsertVoiceWorkCreators(ingest, upsertContext);
|
UpsertVoiceWorkCreators(ingest, upsertContext);
|
||||||
|
UpsertVoiceWorkSupportedLanguages(ingest, upsertContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpsertCircle(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
private void UpsertCircle(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
@@ -85,23 +114,40 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
private void UpsertVoiceWork(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
private void UpsertVoiceWork(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
{
|
{
|
||||||
VoiceWork voiceWork = GetOrAddVoiceWork(ingest, upsertContext);
|
VoiceWork voiceWork = GetOrAddVoiceWork(ingest, upsertContext);
|
||||||
|
|
||||||
|
bool isAdded = dbContext.Entry(voiceWork).State == EntityState.Added;
|
||||||
|
bool isWithinCurrentScanAnchor = voiceWork.LastScannedDate == upsertContext.CurrentScanAnchor;
|
||||||
|
bool isNewOnSale = voiceWork.SalesDate is null && ingest.SalesDate is not null;
|
||||||
|
|
||||||
|
bool isNew = isAdded || isWithinCurrentScanAnchor || isNewOnSale;
|
||||||
|
|
||||||
voiceWork.Circle = upsertContext.Circles[ingest.MakerId];
|
voiceWork.Circle = upsertContext.Circles[ingest.MakerId];
|
||||||
voiceWork.ProductName = ingest.Title;
|
voiceWork.ProductName = ingest.Title;
|
||||||
voiceWork.Description = ingest.Description;
|
voiceWork.Description = ingest.Description;
|
||||||
|
voiceWork.HasImage = ingest.HasImage;
|
||||||
|
voiceWork.Rating = (int)ingest.AgeRating;
|
||||||
voiceWork.Downloads = ingest.Downloads;
|
voiceWork.Downloads = ingest.Downloads;
|
||||||
voiceWork.WishlistCount = ingest.WishlistCount;
|
voiceWork.WishlistCount = ingest.WishlistCount;
|
||||||
voiceWork.HasTrial = ingest.HasTrial;
|
voiceWork.HasTrial = ingest.HasTrial;
|
||||||
voiceWork.HasChobit = ingest.HasDLPlay;
|
voiceWork.HasChobit = ingest.HasDLPlay;
|
||||||
voiceWork.StarRating = ingest.StarRating;
|
voiceWork.StarRating = ingest.StarRating;
|
||||||
voiceWork.Votes = ingest.Votes;
|
voiceWork.Votes = ingest.Votes;
|
||||||
|
|
||||||
voiceWork.IsValid = true;
|
voiceWork.IsValid = true;
|
||||||
|
|
||||||
//var est = i.EstimatedDate?.ToDateTime(new TimeOnly(0, 0));
|
if (ingest.SalesDate.HasValue)
|
||||||
//if (vw2.ExpectedDate != est) { vw2.ExpectedDate = est; changed = true; }
|
{
|
||||||
|
voiceWork.SalesDate = ingest.SalesDate.Value.ToDateTime(new TimeOnly(0, 0));
|
||||||
//var sales = i.SalesDate?.ToDateTime(new TimeOnly(0, 0));
|
voiceWork.ExpectedDate = null;
|
||||||
//if (vw2.SalesDate != sales) { vw2.SalesDate = sales; changed = true; }
|
voiceWork.PlannedReleaseDate = null;
|
||||||
|
voiceWork.Status = isNew ? (byte)VoiceWorkStatus.NewRelease : (byte)VoiceWorkStatus.Available;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
voiceWork.SalesDate = null;
|
||||||
|
voiceWork.ExpectedDate = ingest.ExpectedDate?.ToDateTime(new TimeOnly(0, 0));
|
||||||
|
voiceWork.PlannedReleaseDate = ingest.RegistrationDate > upsertContext.CurrentScanAnchor ? ingest.RegistrationDate : null;
|
||||||
|
voiceWork.Status = isNew ? (byte)VoiceWorkStatus.NewAndUpcoming : (byte)VoiceWorkStatus.Upcoming;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private VoiceWork GetOrAddVoiceWork(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
private VoiceWork GetOrAddVoiceWork(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
@@ -144,17 +190,17 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpsertCreators(VoiceWorkIngest ingest, VoiceWorkUpsertContext something)
|
private void UpsertCreators(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
{
|
{
|
||||||
foreach (string creatorName in ingest.Creators)
|
foreach (string creatorName in ingest.Creators)
|
||||||
{
|
{
|
||||||
GetOrAddCreator(creatorName, something);
|
GetOrAddCreator(creatorName, upsertContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Creator GetOrAddCreator(string creatorName, VoiceWorkUpsertContext something)
|
private Creator GetOrAddCreator(string creatorName, VoiceWorkUpsertContext upsertContext)
|
||||||
{
|
{
|
||||||
if (!something.Creators.TryGetValue(creatorName, out Creator? creator))
|
if (!upsertContext.Creators.TryGetValue(creatorName, out Creator? creator))
|
||||||
{
|
{
|
||||||
creator = new Creator
|
creator = new Creator
|
||||||
{
|
{
|
||||||
@@ -162,22 +208,22 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
};
|
};
|
||||||
|
|
||||||
dbContext.Creators.Add(creator);
|
dbContext.Creators.Add(creator);
|
||||||
something.Creators[creatorName] = creator;
|
upsertContext.Creators[creatorName] = creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
return creator;
|
return creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpsertVoiceWorkTags(VoiceWorkIngest ingest, VoiceWorkUpsertContext something)
|
private void UpsertVoiceWorkTags(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
{
|
{
|
||||||
VoiceWork voiceWork = something.VoiceWorks[ingest.ProductId];
|
VoiceWork voiceWork = upsertContext.VoiceWorks[ingest.ProductId];
|
||||||
Dictionary<int, VoiceWorkTag> existingTagLinks = voiceWork.VoiceWorkTags.ToDictionary(x => x.TagId);
|
Dictionary<int, VoiceWorkTag> existingTagLinks = voiceWork.Tags.ToDictionary(x => x.TagId);
|
||||||
|
|
||||||
int position = 1;
|
int position = 1;
|
||||||
|
|
||||||
foreach (string tagName in ingest.Tags)
|
foreach (string tagName in ingest.Tags)
|
||||||
{
|
{
|
||||||
Tag tag = something.Tags[tagName];
|
Tag tag = upsertContext.Tags[tagName];
|
||||||
|
|
||||||
if (!existingTagLinks.TryGetValue(tag.TagId, out VoiceWorkTag? voiceWorkTag))
|
if (!existingTagLinks.TryGetValue(tag.TagId, out VoiceWorkTag? voiceWorkTag))
|
||||||
{
|
{
|
||||||
@@ -195,16 +241,16 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpsertVoiceWorkCreators(VoiceWorkIngest ingest, VoiceWorkUpsertContext something)
|
private void UpsertVoiceWorkCreators(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
{
|
{
|
||||||
VoiceWork voiceWork = something.VoiceWorks[ingest.ProductId];
|
VoiceWork voiceWork = upsertContext.VoiceWorks[ingest.ProductId];
|
||||||
Dictionary<int, VoiceWorkCreator> existingCreatorLinks = voiceWork.VoiceWorkCreators.ToDictionary(x => x.CreatorId);
|
Dictionary<int, VoiceWorkCreator> existingCreatorLinks = voiceWork.Creators.ToDictionary(x => x.CreatorId);
|
||||||
|
|
||||||
int position = 1;
|
int position = 1;
|
||||||
|
|
||||||
foreach (string creatorName in ingest.Creators)
|
foreach (string creatorName in ingest.Creators)
|
||||||
{
|
{
|
||||||
Creator creator = something.Creators[creatorName];
|
Creator creator = upsertContext.Creators[creatorName];
|
||||||
|
|
||||||
if (!existingCreatorLinks.TryGetValue(creator.CreatorId, out VoiceWorkCreator? voiceWorkCreator))
|
if (!existingCreatorLinks.TryGetValue(creator.CreatorId, out VoiceWorkCreator? voiceWorkCreator))
|
||||||
{
|
{
|
||||||
@@ -222,6 +268,26 @@ public class VoiceWorkWriter(AppDbContext dbContext) : IVoiceWorkWriter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpsertVoiceWorkSupportedLanguages(VoiceWorkIngest ingest, VoiceWorkUpsertContext upsertContext)
|
||||||
|
{
|
||||||
|
VoiceWork voiceWork = upsertContext.VoiceWorks[ingest.ProductId];
|
||||||
|
Dictionary<string, VoiceWorkSupportedLanguage> existingLanguageLinks = voiceWork.SupportedLanguages.ToDictionary(x => x.Language);
|
||||||
|
|
||||||
|
foreach (ISupportedLanguage supportedLanguage in ingest.SupportedLanguages)
|
||||||
|
{
|
||||||
|
if (!existingLanguageLinks.TryGetValue(supportedLanguage.Code, out VoiceWorkSupportedLanguage? voiceWorkSupportedLanguage))
|
||||||
|
{
|
||||||
|
voiceWorkSupportedLanguage = new VoiceWorkSupportedLanguage
|
||||||
|
{
|
||||||
|
VoiceWork = voiceWork,
|
||||||
|
Language = supportedLanguage.Code
|
||||||
|
};
|
||||||
|
|
||||||
|
dbContext.VoiceWorkSupportedLanguages.Add(voiceWorkSupportedLanguage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<SetVoiceWorkFavoriteResponse> SetFavoriteAsync(SetVoiceWorkFavoriteRequest request, CancellationToken cancellationToken)
|
public async Task<SetVoiceWorkFavoriteResponse> SetFavoriteAsync(SetVoiceWorkFavoriteRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
VoiceWork voiceWork = await GetVoiceWorkAsync(request.VoiceWorkId, cancellationToken);
|
VoiceWork voiceWork = await GetVoiceWorkAsync(request.VoiceWorkId, cancellationToken);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using JSMR.Application.Common;
|
using JSMR.Application.Common;
|
||||||
using JSMR.Application.Integrations.DLSite.Models;
|
using JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
using JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
using JSMR.Infrastructure.Integrations.DLSite.Models;
|
using JSMR.Infrastructure.Integrations.DLSite.Models;
|
||||||
|
|
||||||
namespace JSMR.Infrastructure.Integrations.DLSite.Mapping;
|
namespace JSMR.Infrastructure.Integrations.DLSite.Mapping;
|
||||||
@@ -23,9 +24,25 @@ public static class DLSiteToDomainMapper
|
|||||||
("CHI_HANS", Language.ChineseSimplified)
|
("CHI_HANS", Language.ChineseSimplified)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static readonly (string Code, ISupportedLanguage Lang)[] SupportedLanguageFlags2 =
|
||||||
|
[
|
||||||
|
("JPN", new JapaneseLanguage()),
|
||||||
|
("ENG", new EnglishLanguage()),
|
||||||
|
("CHI", new ChineseLanguage()),
|
||||||
|
("CHI_HANT", new TraditionalChineseLanguage()),
|
||||||
|
("CHI_HANS", new SimplifiedChineseLanguage())
|
||||||
|
];
|
||||||
|
|
||||||
private static readonly Dictionary<string, Language> TranslationLanguageMap =
|
private static readonly Dictionary<string, Language> TranslationLanguageMap =
|
||||||
SupportedLanguageFlags.ToDictionary(x => x.Code, x => x.Lang, StringComparer.OrdinalIgnoreCase);
|
SupportedLanguageFlags.ToDictionary(x => x.Code, x => x.Lang, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static readonly Dictionary<int, AgeRating> AgeRatingMap = new()
|
||||||
|
{
|
||||||
|
{ 1, AgeRating.AllAges },
|
||||||
|
{ 2, AgeRating.R15 },
|
||||||
|
{ 3, AgeRating.R18 }
|
||||||
|
};
|
||||||
|
|
||||||
public static VoiceWorkDetailCollection Map(ProductInfoCollection? productInfoCollection)
|
public static VoiceWorkDetailCollection Map(ProductInfoCollection? productInfoCollection)
|
||||||
{
|
{
|
||||||
VoiceWorkDetailCollection result = [];
|
VoiceWorkDetailCollection result = [];
|
||||||
@@ -63,7 +80,8 @@ public static class DLSiteToDomainMapper
|
|||||||
AI = MapAIGeneration(optionsSet),
|
AI = MapAIGeneration(optionsSet),
|
||||||
HasTrial = optionsSet.Contains(OptTrial),
|
HasTrial = optionsSet.Contains(OptTrial),
|
||||||
HasDLPlay = optionsSet.Contains(OptDLPlay),
|
HasDLPlay = optionsSet.Contains(OptDLPlay),
|
||||||
HasReviews = optionsSet.Contains(OptReviews)
|
HasReviews = optionsSet.Contains(OptReviews),
|
||||||
|
AgeRating = MapAgeRating(productInfo)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,11 +123,11 @@ public static class DLSiteToDomainMapper
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Language[] MapSupportedLanguages(HashSet<string> options)
|
private static ISupportedLanguage[] MapSupportedLanguages(HashSet<string> options)
|
||||||
{
|
{
|
||||||
List<Language> languages = [];
|
List<ISupportedLanguage> languages = [];
|
||||||
|
|
||||||
foreach (var (code, language) in SupportedLanguageFlags)
|
foreach (var (code, language) in SupportedLanguageFlags2)
|
||||||
{
|
{
|
||||||
if (options.Contains(code) && !languages.Contains(language))
|
if (options.Contains(code) && !languages.Contains(language))
|
||||||
languages.Add(language);
|
languages.Add(language);
|
||||||
@@ -128,4 +146,12 @@ public static class DLSiteToDomainMapper
|
|||||||
|
|
||||||
return AIGeneration.None;
|
return AIGeneration.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AgeRating MapAgeRating(ProductInfo productInfo)
|
||||||
|
{
|
||||||
|
if (AgeRatingMap.TryGetValue(productInfo.AgeCategory, out AgeRating ageRating))
|
||||||
|
return ageRating;
|
||||||
|
|
||||||
|
return AgeRating.R18;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,12 +7,21 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.3" />
|
<None Remove="Common\Languages\Language.xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Common\Languages\Language.xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using JSMR.Infrastructure.Common.Locales;
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Infrastructure.Common.Locales;
|
||||||
using JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
|
|
||||||
namespace JSMR.Infrastructure.Scanning;
|
namespace JSMR.Infrastructure.Scanning;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using JSMR.Infrastructure.Common.Locales;
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Infrastructure.Common.Locales;
|
||||||
using JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
using JSMR.Infrastructure.Http;
|
using JSMR.Infrastructure.Http;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using JSMR.Infrastructure.Common.Locales;
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Infrastructure.Common.Locales;
|
||||||
using JSMR.Infrastructure.Common.SupportedLanguages;
|
using JSMR.Infrastructure.Common.SupportedLanguages;
|
||||||
using JSMR.Infrastructure.Http;
|
using JSMR.Infrastructure.Http;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using HtmlAgilityPack;
|
using HtmlAgilityPack;
|
||||||
|
using JSMR.Application.Common;
|
||||||
using JSMR.Application.Scanning.Contracts;
|
using JSMR.Application.Scanning.Contracts;
|
||||||
using JSMR.Application.Scanning.Ports;
|
using JSMR.Application.Scanning.Ports;
|
||||||
using JSMR.Infrastructure.Common.Locales;
|
using JSMR.Infrastructure.Common.Locales;
|
||||||
@@ -7,6 +8,7 @@ using JSMR.Infrastructure.Http;
|
|||||||
using JSMR.Infrastructure.Scanning.Models;
|
using JSMR.Infrastructure.Scanning.Models;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace JSMR.Infrastructure.Scanning;
|
namespace JSMR.Infrastructure.Scanning;
|
||||||
|
|
||||||
@@ -90,7 +92,8 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
|
|||||||
ImageUrl = imageUrl,
|
ImageUrl = imageUrl,
|
||||||
Type = imageUrl.Contains("ana/doujin") ? DLSiteWorkType.Announced : DLSiteWorkType.Released,
|
Type = imageUrl.Contains("ana/doujin") ? DLSiteWorkType.Announced : DLSiteWorkType.Released,
|
||||||
StarRating = rating?.Score,
|
StarRating = rating?.Score,
|
||||||
Votes = rating?.Votes
|
Votes = rating?.Votes,
|
||||||
|
AgeRating = GetAgeRating(node.GenreNodes)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (node.ExpectedDateNode != null)
|
if (node.ExpectedDateNode != null)
|
||||||
@@ -111,6 +114,19 @@ public abstract class VoiceWorksScanner(IHtmlLoader htmlLoader) : IVoiceWorksSca
|
|||||||
return work;
|
return work;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AgeRating GetAgeRating(List<HtmlNode> genreNodes)
|
||||||
|
{
|
||||||
|
List<string> genres = ScannerUtilities.GetStringListFromNodes(genreNodes);
|
||||||
|
|
||||||
|
if (genres.Contains("全年齢"))
|
||||||
|
return AgeRating.AllAges;
|
||||||
|
|
||||||
|
if (genres.Contains("R-15"))
|
||||||
|
return AgeRating.R15;
|
||||||
|
|
||||||
|
return AgeRating.R18;
|
||||||
|
}
|
||||||
|
|
||||||
private static ScannedRating? GetScannedRating(HtmlNode starRatingNode)
|
private static ScannedRating? GetScannedRating(HtmlNode starRatingNode)
|
||||||
{
|
{
|
||||||
if (starRatingNode == null)
|
if (starRatingNode == null)
|
||||||
|
|||||||
201
JSMR.Tests/Fixtures/VoiceWorkUpsertFixture.cs
Normal file
201
JSMR.Tests/Fixtures/VoiceWorkUpsertFixture.cs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
using JSMR.Application.Scanning.Contracts;
|
||||||
|
using JSMR.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace JSMR.Tests.Fixtures;
|
||||||
|
|
||||||
|
public class VoiceWorkUpsertFixture : MariaDbFixture
|
||||||
|
{
|
||||||
|
protected override async Task OnInitializedAsync(AppDbContext context)
|
||||||
|
{
|
||||||
|
await SeedAsync(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SeedAsync(AppDbContext context)
|
||||||
|
{
|
||||||
|
if (await context.VoiceWorks.AnyAsync())
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.Circles.AddRange(
|
||||||
|
new() { CircleId = 1, Name = "Good Dreams", MakerId = "RG00001" },
|
||||||
|
new() { CircleId = 2, Name = "Sweet Dreams", Favorite = true, MakerId = "RG00002" },
|
||||||
|
new() { CircleId = 3, Name = "Nightmare Fuel", Blacklisted = true, MakerId = "RG00003" }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.VoiceWorks.AddRange(
|
||||||
|
new() { VoiceWorkId = 1, CircleId = 1, ProductId = "RJ0000001", ProductName = "Today Sounds", Description = "An average product.", Status = (byte)VoiceWorkStatus.Available, SalesDate = new(2025, 1, 1), Downloads = 500, WishlistCount = 750, StarRating = 35 },
|
||||||
|
new() { VoiceWorkId = 2, CircleId = 2, ProductId = "RJ0000002", ProductName = "Super Comfy ASMR", Description = "An amazing product!", Status = (byte)VoiceWorkStatus.NewRelease, SalesDate = new(2025, 1, 3), Downloads = 5000, WishlistCount = 12000, StarRating = 50, Favorite = true },
|
||||||
|
new() { VoiceWorkId = 3, CircleId = 3, ProductId = "RJ0000003", ProductName = "Low Effort", Description = "A bad product.", Status = (byte)VoiceWorkStatus.Available, SalesDate = new(2025, 1, 2), Downloads = 50, WishlistCount = 100, StarRating = 20 },
|
||||||
|
new() { VoiceWorkId = 4, CircleId = 1, ProductId = "RJ0000004", ProductName = "Tomorrow Sounds", Description = "A average upcoming product.", Status = (byte)VoiceWorkStatus.Upcoming, ExpectedDate = new(2025, 1, 1), WishlistCount = 300 },
|
||||||
|
new() { VoiceWorkId = 5, CircleId = 2, ProductId = "RJ0000005", ProductName = "Super Comfy ASMR+", Description = "All your favorite sounds, plus more!", Status = (byte)VoiceWorkStatus.NewAndUpcoming, ExpectedDate = new(2025, 1, 11), WishlistCount = 10000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.Tags.AddRange(
|
||||||
|
new() { TagId = 1, Name = "ASMR" },
|
||||||
|
new() { TagId = 2, Name = "OL" },
|
||||||
|
new() { TagId = 3, Name = "ほのぼの" },
|
||||||
|
new() { TagId = 4, Name = "エルフ/妖精" },
|
||||||
|
new() { TagId = 5, Name = "ツンデレ", Favorite = true },
|
||||||
|
new() { TagId = 6, Name = "オールハッピー" },
|
||||||
|
new() { TagId = 7, Name = "ギャル" },
|
||||||
|
new() { TagId = 8, Name = "メイド" },
|
||||||
|
new() { TagId = 9, Name = "ノンフィクション/体験談", Blacklisted = true }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.EnglishTags.AddRange(
|
||||||
|
new() { EnglishTagId = 1, TagId = 1, Name = "ASMR" },
|
||||||
|
new() { EnglishTagId = 2, TagId = 2, Name = "Office Lady" },
|
||||||
|
new() { EnglishTagId = 3, TagId = 3, Name = "Heartwarming" },
|
||||||
|
new() { EnglishTagId = 4, TagId = 4, Name = "Elf / Fairy" },
|
||||||
|
new() { EnglishTagId = 5, TagId = 5, Name = "Tsundere" },
|
||||||
|
new() { EnglishTagId = 6, TagId = 6, Name = "All Happy" },
|
||||||
|
new() { EnglishTagId = 7, TagId = 7, Name = "Gal" },
|
||||||
|
new() { EnglishTagId = 8, TagId = 8, Name = "Maid" },
|
||||||
|
new() { EnglishTagId = 9, TagId = 9, Name = "Non-Fiction / Narrative" }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.VoiceWorkTags.AddRange(
|
||||||
|
new() { VoiceWorkId = 1, TagId = 1 }, // ASMR
|
||||||
|
new() { VoiceWorkId = 1, TagId = 2 }, // Office Lady
|
||||||
|
|
||||||
|
new() { VoiceWorkId = 2, TagId = 1 }, // ASMR
|
||||||
|
new() { VoiceWorkId = 2, TagId = 3 }, // Heartwarming
|
||||||
|
new() { VoiceWorkId = 2, TagId = 4 }, // Elf / Fairy
|
||||||
|
new() { VoiceWorkId = 2, TagId = 5 }, // Tsundere
|
||||||
|
new() { VoiceWorkId = 2, TagId = 6 }, // All Happy
|
||||||
|
new() { VoiceWorkId = 2, TagId = 7 }, // Gal
|
||||||
|
new() { VoiceWorkId = 2, TagId = 8 }, // Maid
|
||||||
|
|
||||||
|
new() { VoiceWorkId = 3, TagId = 5 }, // Tsundere
|
||||||
|
new() { VoiceWorkId = 3, TagId = 9 } // Non-Fiction / Narrative
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 5 } // Tsundere
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.Creators.AddRange(
|
||||||
|
new() { CreatorId = 1, Name = "陽向葵ゅか", Favorite = true },
|
||||||
|
new() { CreatorId = 2, Name = "秋野かえで" },
|
||||||
|
new() { CreatorId = 3, Name = "柚木つばめ" },
|
||||||
|
new() { CreatorId = 4, Name = "逢坂成美" },
|
||||||
|
new() { CreatorId = 5, Name = "山田じぇみ子", Blacklisted = true }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.VoiceWorkCreators.AddRange(
|
||||||
|
new() { VoiceWorkId = 1, CreatorId = 2 }, // 秋野かえで
|
||||||
|
|
||||||
|
new() { VoiceWorkId = 2, CreatorId = 1 }, // 陽向葵ゅか
|
||||||
|
|
||||||
|
new() { VoiceWorkId = 3, CreatorId = 5 }, // 山田じぇみ子
|
||||||
|
new() { VoiceWorkId = 3, CreatorId = 1 }, // 陽向葵ゅか
|
||||||
|
|
||||||
|
new() { VoiceWorkId = 4, CreatorId = 3 }, // 柚木つばめ
|
||||||
|
|
||||||
|
new() { VoiceWorkId = 5, CreatorId = 1 }, // 陽向葵ゅか
|
||||||
|
new() { VoiceWorkId = 5, CreatorId = 4 } // 逢坂成美
|
||||||
|
);
|
||||||
|
|
||||||
|
// <Product Id> <Maker Id> <Circle Name> <Product Name> <Product Description> <Tags> <Creators>
|
||||||
|
context.VoiceWorkSearches.AddRange(
|
||||||
|
new() { VoiceWorkId = 1, SearchText = "RJ0000001 RG00001 Good Dreams Today Sounds An average product. ASMR Office Lady" },
|
||||||
|
new() { VoiceWorkId = 2, SearchText = "RJ0000002 RG00002 Sweet Dreams Super Comfy ASMR An amazing product! ASMR Heartwarming Elf / Fairy Tsundere All Happy Gal Maid" },
|
||||||
|
new() { VoiceWorkId = 3, SearchText = "RJ0000003 RG00003 Nightmare Fuel Low Effort A bad product." },
|
||||||
|
new() { VoiceWorkId = 4, SearchText = "RJ0000004 RG00001 Good Dreams Tomorrow Sounds A average upcoming product." },
|
||||||
|
new() { VoiceWorkId = 5, SearchText = "RJ0000005 RG00002 Sweet Dreams Super Comfy ASMR+ All your favorite sounds, plus more!" }
|
||||||
|
);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VoiceWorkIngest[] GetFirstRoundIngests()
|
||||||
|
{
|
||||||
|
DLSiteWork work1 = new()
|
||||||
|
{
|
||||||
|
ProductId = "001",
|
||||||
|
ProductName = "fdsfs",
|
||||||
|
MakerId = "RG00001",
|
||||||
|
Maker = "",
|
||||||
|
ImageUrl = "",
|
||||||
|
SmallImageUrl = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
VoiceWorkDetails details1 = new()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.R18,
|
||||||
|
};
|
||||||
|
|
||||||
|
VoiceWorkIngest[] ingests =
|
||||||
|
[
|
||||||
|
new(work1, details1)
|
||||||
|
{
|
||||||
|
MakerId = "RG00001",
|
||||||
|
MakerName = "Good Dreams",
|
||||||
|
ProductId = "RJ0000001",
|
||||||
|
Title = "Today Sounds",
|
||||||
|
Description = "An average product.",
|
||||||
|
Tags = ["ASMR", "Office Lady"],
|
||||||
|
AgeRating = AgeRating.R15,
|
||||||
|
SalesDate = new DateOnly(2025, 1, 1),
|
||||||
|
ExpectedDate = null,
|
||||||
|
WishlistCount = 750,
|
||||||
|
Downloads = 500,
|
||||||
|
HasTrial = true,
|
||||||
|
HasDLPlay = true
|
||||||
|
},
|
||||||
|
new(work1, details1)
|
||||||
|
{
|
||||||
|
MakerId = "RG00002",
|
||||||
|
MakerName = "Sweet Dreams",
|
||||||
|
ProductId = "RJ0000002",
|
||||||
|
Title = "Super Comfy ASMR",
|
||||||
|
Description = "An amazing product!",
|
||||||
|
AgeRating = AgeRating.AllAges,
|
||||||
|
Tags = ["ASMR", "Heartwarming", "Elf / Fairy", "Tsundere", "All Happy", "Gal", "Maid"],
|
||||||
|
SalesDate = new DateOnly(2025, 3, 1),
|
||||||
|
ExpectedDate = null,
|
||||||
|
WishlistCount = 12000,
|
||||||
|
Downloads = 5000,
|
||||||
|
HasTrial = true,
|
||||||
|
HasDLPlay = true
|
||||||
|
},
|
||||||
|
new(work1, details1)
|
||||||
|
{
|
||||||
|
MakerId = "RG00003",
|
||||||
|
MakerName = "Nightmare Fuel",
|
||||||
|
ProductId = "RJ0000003",
|
||||||
|
Title = "Low Effort",
|
||||||
|
Description = "A bad product.",
|
||||||
|
Tags = ["Tsundere", "Non-Fiction / Narrative"],
|
||||||
|
AgeRating = AgeRating.R18,
|
||||||
|
SalesDate = new DateOnly(2025, 1, 1),
|
||||||
|
ExpectedDate = null,
|
||||||
|
WishlistCount = 100,
|
||||||
|
Downloads = 50,
|
||||||
|
HasTrial = true,
|
||||||
|
HasDLPlay = false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return ingests;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
JSMR.Tests/Integration/VoiceWorkUpsertTests.cs
Normal file
47
JSMR.Tests/Integration/VoiceWorkUpsertTests.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using JSMR.Application.Integrations.DLSite.Models;
|
||||||
|
using JSMR.Application.Scanning.Contracts;
|
||||||
|
using JSMR.Infrastructure.Common.Time;
|
||||||
|
using JSMR.Infrastructure.Data;
|
||||||
|
using JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
|
using JSMR.Tests.Fixtures;
|
||||||
|
using NSubstitute;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace JSMR.Tests.Integration;
|
||||||
|
|
||||||
|
public class VoiceWorkUpsertTests(VoiceWorkUpsertFixture fixture) : IClassFixture<VoiceWorkUpsertFixture>
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_None()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
|
||||||
|
ITimeProvider timeProvider = Substitute.For<ITimeProvider>();
|
||||||
|
timeProvider.Now().Returns(new DateTimeOffset(2025, 10, 1, 0, 0, 0, 0, TimeSpan.FromSeconds(0)));
|
||||||
|
|
||||||
|
DLSiteWork work1 = new()
|
||||||
|
{
|
||||||
|
ProductId = "001",
|
||||||
|
ProductName = "fdsfs",
|
||||||
|
MakerId = "RG00001",
|
||||||
|
Maker = "",
|
||||||
|
ImageUrl = "",
|
||||||
|
SmallImageUrl = ""
|
||||||
|
};
|
||||||
|
|
||||||
|
VoiceWorkDetails details1 = new()
|
||||||
|
{
|
||||||
|
AgeRating = Application.Common.AgeRating.R18,
|
||||||
|
};
|
||||||
|
|
||||||
|
VoiceWorkIngest[] ingests =
|
||||||
|
[
|
||||||
|
new(work1, details1)
|
||||||
|
];
|
||||||
|
|
||||||
|
VoiceWorkWriter writer = new(context, timeProvider);
|
||||||
|
await writer.UpsertAsync(ingests, CancellationToken.None);
|
||||||
|
|
||||||
|
context.VoiceWorks.Count().ShouldBe(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,14 +22,14 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||||
<PackageReference Include="Testcontainers" Version="4.7.0" />
|
<PackageReference Include="Testcontainers" Version="4.7.0" />
|
||||||
<PackageReference Include="Testcontainers.MariaDb" Version="4.7.0" />
|
<PackageReference Include="Testcontainers.MariaDb" Version="4.7.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
Reference in New Issue
Block a user